Index: trunk/extensions/CentralAuth/CentralAuthUser.php |
— | — | @@ -166,10 +166,6 @@ |
167 | 167 | 'mu_dbname' => $dbname, |
168 | 168 | 'mu_local_id' => $row->user_id, |
169 | 169 | 'mu_name' => $row->user_name, |
170 | | - 'mu_password' => $row->user_password, |
171 | | - 'mu_email' => $row->user_email, |
172 | | - 'mu_email_authenticated' => $row->user_email_authenticated, |
173 | | - 'mu_editcount' => $row->user_editcount, |
174 | 170 | ), |
175 | 171 | __METHOD__, |
176 | 172 | array( 'IGNORE' ) ); |
— | — | @@ -212,7 +208,7 @@ |
213 | 209 | * @fixme add some locking or something |
214 | 210 | */ |
215 | 211 | function attemptAutoMigration() { |
216 | | - $rows = $this->fetchUnattached(); |
| 212 | + $rows = $this->queryUnattached(); |
217 | 213 | |
218 | 214 | if( !$rows ) { |
219 | 215 | wfDebugLog( 'CentralAuth', |
— | — | @@ -227,33 +223,35 @@ |
228 | 224 | // We have to pick a master account |
229 | 225 | // The winner is the one with the most edits, usually |
230 | 226 | foreach( $rows as $row ) { |
231 | | - if( $row->mu_editcount > $max ) { |
| 227 | + if( $row['editCount'] > $max ) { |
232 | 228 | $winner = $row; |
233 | | - $max = $row->mu_editcount; |
| 229 | + $max = $row['editCount']; |
234 | 230 | } |
235 | 231 | } |
236 | 232 | if( !isset( $winner ) ) { |
237 | | - var_dump( $rows ); |
238 | | - die(); |
| 233 | + throw new MWException( "Logic error in migration: " . |
| 234 | + "Unable to determine primary account for $this->mName" ); |
239 | 235 | } |
240 | 236 | |
241 | 237 | // If the primary account has an e-mail address set, |
242 | 238 | // we can use it to match other accounts. If it doesn't, |
243 | 239 | // we can't be sure that the other accounts with no mail |
244 | 240 | // are the same person, so err on the side of caution. |
245 | | - $winningMail = ($winner->mu_email == '' |
| 241 | + $winningMail = ($winner['email'] == '' |
246 | 242 | ? false |
247 | | - : $winner->mu_email); |
| 243 | + : $winner['email']); |
248 | 244 | |
| 245 | + if( $this->mName == 'WikiSysop' ) { var_dump( $rows ); } |
249 | 246 | foreach( $rows as $row ) { |
250 | | - if( $row->mu_dbname == $winner->mu_dbname ) { |
| 247 | + $local = $this->mName . "@" . $row['dbName']; |
| 248 | + if( $row['dbName'] == $winner['dbName'] ) { |
251 | 249 | // Primary account holder... duh |
252 | 250 | $method = 'primary'; |
253 | | - } elseif( $row->mu_email === $winningMail ) { |
| 251 | + } elseif( $row['email'] === $winningMail ) { |
254 | 252 | // Same e-mail as primary means we know they could |
255 | 253 | // reset their password, so we give them the account. |
256 | 254 | $method = 'mail'; |
257 | | - } elseif( $row->mu_editcount == 0 ) { |
| 255 | + } elseif( $row['editCount'] == 0 ) { |
258 | 256 | // Unused accounts are fair game for reclaiming |
259 | 257 | $method = 'empty'; |
260 | 258 | } else { |
— | — | @@ -262,16 +260,18 @@ |
263 | 261 | // If the password matches, it will be automigrated |
264 | 262 | // at next login. If no match, user will have to input |
265 | 263 | // the conflicting password or deal with the conflict. |
266 | | - break; |
| 264 | + wfDebugLog( 'CentralAuth', "unresolvable $local" ); |
| 265 | + continue; |
267 | 266 | } |
268 | | - $attach[] = array( $row->mu_dbname, $row->mu_local_id, $method ); |
| 267 | + wfDebugLog( 'CentralAuth', "$method $local" ); |
| 268 | + $attach[] = array( $row['dbName'], $row['localId'], $method ); |
269 | 269 | } |
270 | 270 | |
271 | 271 | $ok = $this->storeGlobalData( |
272 | | - $winner->mu_local_id, |
273 | | - $winner->mu_password, |
274 | | - $winner->mu_email, |
275 | | - $winner->mu_email_authenticated ); |
| 272 | + $winner['localId'], |
| 273 | + $winner['password'], |
| 274 | + $winner['email'], |
| 275 | + $winner['emailAuthenticated'] ); |
276 | 276 | |
277 | 277 | if( !$ok ) { |
278 | 278 | wfDebugLog( 'CentralAuth', |
— | — | @@ -285,7 +285,7 @@ |
286 | 286 | } else { |
287 | 287 | if( count( $rows ) == 1 ) { |
288 | 288 | wfDebugLog( 'CentralAuth', |
289 | | - "Singleton migration for '$this->mName' on $winner->mu_dbname" ); |
| 289 | + "Singleton migration for '$this->mName' on " . $winner['dbName'] ); |
290 | 290 | } else { |
291 | 291 | wfDebugLog( 'CentralAuth', |
292 | 292 | "Full automatic migration for '$this->mName'" ); |
— | — | @@ -312,7 +312,7 @@ |
313 | 313 | * @return bool true if all accounts are migrated at the end |
314 | 314 | */ |
315 | 315 | function attemptPasswordMigration( $password, &$migrated=null, &$remaining=null ) { |
316 | | - $rows = $this->fetchUnattached(); |
| 316 | + $rows = $this->queryUnattached(); |
317 | 317 | |
318 | 318 | if( count( $rows ) == 0 ) { |
319 | 319 | wfDebugLog( 'CentralAuth', |
— | — | @@ -325,15 +325,16 @@ |
326 | 326 | |
327 | 327 | // Look for accounts we can match by password |
328 | 328 | foreach( $rows as $key => $row ) { |
329 | | - if( $this->matchHash( $password, $row->mu_local_id, $row->mu_password ) ) { |
| 329 | + $db = $row['dbName']; |
| 330 | + if( $this->matchHash( $password, $row['localId'], $row['password'] ) ) { |
330 | 331 | wfDebugLog( 'CentralAuth', |
331 | | - "Attaching '$this->mName' on $row->mu_dbname by password" ); |
332 | | - $this->attach( $row->mu_dbname, $row->mu_local_id, 'password' ); |
333 | | - $migrated[] = $row->mu_dbname; |
| 332 | + "Attaching '$this->mName' on $db by password" ); |
| 333 | + $this->attach( $db, $row['localId'], 'password' ); |
| 334 | + $migrated[] = $db; |
334 | 335 | } else { |
335 | 336 | wfDebugLog( 'CentralAuth', |
336 | | - "No password match for '$this->mName' on $row->mu_dbname" ); |
337 | | - $remaining[] = $row->mu_dbname; |
| 337 | + "No password match for '$this->mName' on $db" ); |
| 338 | + $remaining[] = $db; |
338 | 339 | } |
339 | 340 | } |
340 | 341 | |
— | — | @@ -364,17 +365,17 @@ |
365 | 366 | |
366 | 367 | public function adminAttach( $list, &$migrated=null, &$remaining=null ) { |
367 | 368 | $valid = $this->validateList( $list ); |
368 | | - $unattached = $this->fetchUnattached(); |
| 369 | + $unattached = $this->queryUnattached(); |
369 | 370 | |
370 | 371 | $migrated = array(); |
371 | 372 | $remaining = array(); |
372 | 373 | |
373 | 374 | foreach( $unattached as $row ) { |
374 | | - if( in_array( $row->mu_dbname, $valid ) ) { |
375 | | - $this->attach( $row->mu_dbname, $row->mu_local_id, 'admin' ); |
376 | | - $migrated[] = $row->mu_dbname; |
| 375 | + if( in_array( $row['dbName'], $valid ) ) { |
| 376 | + $this->attach( $row['dbName'], $row['localId'], 'admin' ); |
| 377 | + $migrated[] = $row['dbName']; |
377 | 378 | } else { |
378 | | - $remaining[] = $row->mu_dbname; |
| 379 | + $remaining[] = $row['dbName']; |
379 | 380 | } |
380 | 381 | } |
381 | 382 | |
— | — | @@ -573,9 +574,20 @@ |
574 | 575 | |
575 | 576 | $items = array(); |
576 | 577 | foreach( $rows as $row ) { |
577 | | - $items[$row->mu_dbname] = array( |
578 | | - 'dbName' => $row->mu_dbname, |
579 | | - 'localId' => intval( $row->mu_local_id ), |
| 578 | + $db = $row->mu_dbname; |
| 579 | + $id = intval( $row->mu_local_id ); |
| 580 | + $userData = self::localUserData( $db, $id ); |
| 581 | + if( !is_object( $userData ) ) { |
| 582 | + throw new MWException("Bad user row looking up local user #$id@$db"); |
| 583 | + } |
| 584 | + |
| 585 | + $items[$db] = array( |
| 586 | + 'dbName' => $db, |
| 587 | + 'localId' => $id, |
| 588 | + 'email' => $userData->user_email, |
| 589 | + 'emailAuthenticated' => $userData->user_email_authenticated, |
| 590 | + 'password' => $userData->user_password, |
| 591 | + 'editCount' => $userData->user_editcount, |
580 | 592 | ); |
581 | 593 | } |
582 | 594 | |
— | — | @@ -583,6 +595,34 @@ |
584 | 596 | } |
585 | 597 | |
586 | 598 | /** |
| 599 | + * Fetch a row of user data needed for migration. |
| 600 | + * @todo: work on multi-master clusters! |
| 601 | + */ |
| 602 | + protected static function localUserData( $dbname, $id ) { |
| 603 | + //$db = wfGetForeignDB( $dbname ); |
| 604 | + $db = wfGetDB( DB_MASTER ); |
| 605 | + $row = $db->selectRow( "`$dbname`.user", |
| 606 | + array( |
| 607 | + 'user_email', |
| 608 | + 'user_email_authenticated', |
| 609 | + 'user_password', |
| 610 | + 'user_editcount' ), |
| 611 | + array( 'user_id' => $id ), |
| 612 | + __METHOD__ ); |
| 613 | + |
| 614 | + // Edit count field may not be initialized... |
| 615 | + if( $row !== false && is_null( $row->user_editcount ) ) { |
| 616 | + $row->user_editcount = $db->selectField( |
| 617 | + "`$dbname`.revision", |
| 618 | + 'COUNT(*)', |
| 619 | + array( 'rev_user' => $id ), |
| 620 | + __METHOD__ ); |
| 621 | + } |
| 622 | + |
| 623 | + return $row; |
| 624 | + } |
| 625 | + |
| 626 | + /** |
587 | 627 | * Find any remaining migration records for this username |
588 | 628 | * which haven't gotten attached to some global account. |
589 | 629 | */ |
Index: trunk/extensions/CentralAuth/central-auth.sql |
— | — | @@ -17,7 +17,7 @@ |
18 | 18 | |
19 | 19 | -- Salt and hashed password |
20 | 20 | gu_salt char(16), -- or should this be an int? usually the old user_id |
21 | | - gu_password char(32), |
| 21 | + gu_password tinyblob, |
22 | 22 | |
23 | 23 | -- If true, this account cannot be used to log in on any wiki. |
24 | 24 | gu_locked bool not null default 0, |
— | — | @@ -30,13 +30,12 @@ |
31 | 31 | gu_registration char(14) binary, |
32 | 32 | |
33 | 33 | -- Random key for password resets |
34 | | - gu_password_reset_key char(32), |
| 34 | + gu_password_reset_key tinyblob, |
35 | 35 | gu_password_reset_expiration char(14) binary, |
36 | 36 | |
37 | 37 | primary key (gu_id), |
38 | 38 | unique key (gu_name), |
39 | | - key (gu_email), |
40 | | - key (gu_password_reset_key) |
| 39 | + key (gu_email) |
41 | 40 | ) TYPE=InnoDB; |
42 | 41 | |
43 | 42 | |
— | — | @@ -74,10 +73,13 @@ |
75 | 74 | unique key (lu_global_id, lu_dbname) |
76 | 75 | ) TYPE=InnoDB; |
77 | 76 | |
| 77 | + |
78 | 78 | -- Migration state table |
79 | 79 | -- |
| 80 | +-- Lists registered usernames on various wikis, used to optimize migration |
| 81 | +-- checks by only having to check dbs where the name is actually registered. |
| 82 | +-- |
| 83 | +-- May be batch-initialized and/or lazy-initialized. |
80 | 84 | -- Once migration is complete, this data can be ignored/discarded. |
81 | 85 | -- |
82 | 86 | CREATE TABLE migrateuser ( |
— | — | @@ -90,23 +92,6 @@ |
91 | 93 | -- Username at migration time |
92 | 94 | mu_name varchar(255) binary, |
93 | 95 | |
94 | | - -- User'd old password hash; salt is lu_id |
95 | | - mu_password varchar(255) binary, |
96 | | - |
97 | | - -- The user_email and user_email_authenticated state from local wiki |
98 | | - mu_email varchar(255) binary, |
99 | | - mu_email_authenticated char(14) binary, |
100 | | - |
101 | | - -- A count of revisions and/or other actions made during migration |
102 | | - -- May be null if it hasn't yet been checked |
103 | | - mu_editcount int, |
104 | | - |
105 | | - -- True if user was blocked... |
106 | | - mu_blocked bool, |
107 | | - |
108 | | - -- True if user has admin privs... |
109 | | - mu_admin bool, |
110 | | - |
111 | 96 | primary key (mu_dbname, mu_local_id), |
112 | 97 | unique key (mu_dbname, mu_name), |
113 | 98 | key (mu_name, mu_dbname) |