Index: trunk/phase3/maintenance/changePassword.php |
— | — | @@ -52,14 +52,7 @@ |
53 | 53 | function main() { |
54 | 54 | $fname = 'ChangePassword::main'; |
55 | 55 | |
56 | | - $this->dbw->update( 'user', |
57 | | - array( |
58 | | - 'user_password' => wfEncryptPassword( $this->user->getId(), $this->password ) |
59 | | - ), |
60 | | - array( |
61 | | - 'user_id' => $this->user->getId() |
62 | | - ), |
63 | | - $fname |
64 | | - ); |
| 56 | + $this->user->setPassword( $this->password ); |
| 57 | + $this->user->saveSettings(); |
65 | 58 | } |
66 | 59 | } |
Index: trunk/phase3/maintenance/updaters.inc |
— | — | @@ -142,6 +142,7 @@ |
143 | 143 | array( 'check_bin', 'protected_titles', 'pt_title', 'patch-pt_title-encoding.sql', ), |
144 | 144 | array( 'maybe_do_profiling_memory_update' ), |
145 | 145 | array( 'do_filearchive_indices_update' ), |
| 146 | + array( 'update_password_format' ), |
146 | 147 | ); |
147 | 148 | |
148 | 149 | |
— | — | @@ -1195,6 +1196,28 @@ |
1196 | 1197 | populate_rev_parent_id( $wgDatabase ); |
1197 | 1198 | } |
1198 | 1199 | |
| 1200 | +function update_password_format() { |
| 1201 | + if ( update_row_exists( 'password format' ) ) { |
| 1202 | + echo "...password hash format already changed\n"; |
| 1203 | + return; |
| 1204 | + } |
| 1205 | + |
| 1206 | + echo "Updating password hash format..."; |
| 1207 | + |
| 1208 | + global $wgDatabase, $wgPasswordSalt; |
| 1209 | + if ( $wgPasswordSalt ) { |
| 1210 | + $sql = "UPDATE user SET user_password=CONCAT(':B:', user_id, ':', user_password) " . |
| 1211 | + "WHERE user_password NOT LIKE ':%'"; |
| 1212 | + } else { |
| 1213 | + $sql = "UPDATE user SET user_password=CONCAT(':A:', user_password) " . |
| 1214 | + "WHERE user_password NOT LIKE ':%'"; |
| 1215 | + } |
| 1216 | + $wgDatabase->query( $sql, __METHOD__ ); |
| 1217 | + $wgDatabase->insert( 'updatelog', array( 'ul_key' => 'password format' ), __METHOD__ ); |
| 1218 | + |
| 1219 | + echo "done\n"; |
| 1220 | +} |
| 1221 | + |
1199 | 1222 | function |
1200 | 1223 | pg_describe_table($table) |
1201 | 1224 | { |
— | — | @@ -1690,3 +1713,4 @@ |
1691 | 1714 | |
1692 | 1715 | return; |
1693 | 1716 | } |
| 1717 | + |
Index: trunk/phase3/includes/User.php |
— | — | @@ -1516,18 +1516,6 @@ |
1517 | 1517 | } |
1518 | 1518 | |
1519 | 1519 | /** |
1520 | | - * Encrypt a password. |
1521 | | - * It can eventually salt a password. |
1522 | | - * @see User::addSalt() |
1523 | | - * @param string $p clear Password. |
1524 | | - * @return string Encrypted password. |
1525 | | - */ |
1526 | | - function encryptPassword( $p ) { |
1527 | | - $this->load(); |
1528 | | - return wfEncryptPassword( $this->mId, $p ); |
1529 | | - } |
1530 | | - |
1531 | | - /** |
1532 | 1520 | * Set the password and reset the random token |
1533 | 1521 | * Calls through to authentication plugin if necessary; |
1534 | 1522 | * will have no effect if the auth plugin refuses to |
— | — | @@ -1579,7 +1567,7 @@ |
1580 | 1568 | // Save an invalid hash... |
1581 | 1569 | $this->mPassword = ''; |
1582 | 1570 | } else { |
1583 | | - $this->mPassword = $this->encryptPassword( $str ); |
| 1571 | + $this->mPassword = self::crypt( $str ); |
1584 | 1572 | } |
1585 | 1573 | $this->mNewpassword = ''; |
1586 | 1574 | $this->mNewpassTime = null; |
— | — | @@ -1623,7 +1611,7 @@ |
1624 | 1612 | */ |
1625 | 1613 | function setNewpassword( $str, $throttle = true ) { |
1626 | 1614 | $this->load(); |
1627 | | - $this->mNewpassword = $this->encryptPassword( $str ); |
| 1615 | + $this->mNewpassword = self::crypt( $str ); |
1628 | 1616 | if ( $throttle ) { |
1629 | 1617 | $this->mNewpassTime = wfTimestampNow(); |
1630 | 1618 | } |
— | — | @@ -2505,14 +2493,13 @@ |
2506 | 2494 | /* Auth plugin doesn't allow local authentication for this user name */ |
2507 | 2495 | return false; |
2508 | 2496 | } |
2509 | | - $ep = $this->encryptPassword( $password ); |
2510 | | - if ( 0 == strcmp( $ep, $this->mPassword ) ) { |
| 2497 | + if ( self::comparePasswords( $this->mPassword, $password, $this->mId ) ) { |
2511 | 2498 | return true; |
2512 | 2499 | } elseif ( function_exists( 'iconv' ) ) { |
2513 | 2500 | # Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted |
2514 | 2501 | # Check for this with iconv |
2515 | | - $cp1252hash = $this->encryptPassword( iconv( 'UTF-8', 'WINDOWS-1252//TRANSLIT', $password ) ); |
2516 | | - if ( 0 == strcmp( $cp1252hash, $this->mPassword ) ) { |
| 2502 | + $cp1252Password = iconv( 'UTF-8', 'WINDOWS-1252//TRANSLIT', $password ); |
| 2503 | + if ( self::comparePasswords( $this->mPassword, $cp1252Password, $this->mId ) ) { |
2517 | 2504 | return true; |
2518 | 2505 | } |
2519 | 2506 | } |
— | — | @@ -2525,8 +2512,7 @@ |
2526 | 2513 | * @return bool |
2527 | 2514 | */ |
2528 | 2515 | function checkTemporaryPassword( $plaintext ) { |
2529 | | - $hash = $this->encryptPassword( $plaintext ); |
2530 | | - return $hash === $this->mNewpassword; |
| 2516 | + return self::comparePasswords( $this->mNewpassword, $plaintext, $this->getId() ); |
2531 | 2517 | } |
2532 | 2518 | |
2533 | 2519 | /** |
— | — | @@ -3001,4 +2987,62 @@ |
3002 | 2988 | ? $right |
3003 | 2989 | : $name; |
3004 | 2990 | } |
| 2991 | + |
| 2992 | + /** |
| 2993 | + * Make an old-style password hash |
| 2994 | + * |
| 2995 | + * @param string $password Plain-text password |
| 2996 | + * @param string $userId User ID |
| 2997 | + */ |
| 2998 | + static function oldCrypt( $password, $userId ) { |
| 2999 | + global $wgPasswordSalt; |
| 3000 | + if ( $wgPasswordSalt ) { |
| 3001 | + return md5( $userId . '-' . md5( $password ) ); |
| 3002 | + } else { |
| 3003 | + return md5( $password ); |
| 3004 | + } |
| 3005 | + } |
| 3006 | + |
| 3007 | + /** |
| 3008 | + * Make a new-style password hash |
| 3009 | + * |
| 3010 | + * @param string $password Plain-text password |
| 3011 | + * @param string $salt Salt, may be random or the user ID. False to generate a salt. |
| 3012 | + */ |
| 3013 | + static function crypt( $password, $salt = false ) { |
| 3014 | + global $wgPasswordSalt; |
| 3015 | + |
| 3016 | + if($wgPasswordSalt) { |
| 3017 | + if ( $salt === false ) { |
| 3018 | + $salt = substr( wfGenerateToken(), 0, 8 ); |
| 3019 | + } |
| 3020 | + return ':B:' . $salt . ':' . md5( $salt . '-' . md5( $password ) ); |
| 3021 | + } else { |
| 3022 | + return ':A:' . md5( $password); |
| 3023 | + } |
| 3024 | + } |
| 3025 | + |
| 3026 | + /** |
| 3027 | + * Compare a password hash with a plain-text password. Requires the user |
| 3028 | + * ID if there's a chance that the hash is an old-style hash. |
| 3029 | + * |
| 3030 | + * @param string $hash Password hash |
| 3031 | + * @param string $password Plain-text password to compare |
| 3032 | + * @param string $userId User ID for old-style password salt |
| 3033 | + */ |
| 3034 | + static function comparePasswords( $hash, $password, $userId = false ) { |
| 3035 | + $m = false; |
| 3036 | + $type = substr( $hash, 0, 3 ); |
| 3037 | + if ( $type == ':A:' ) { |
| 3038 | + # Unsalted |
| 3039 | + return md5( $password ) === substr( $hash, 3 ); |
| 3040 | + } elseif ( $type == ':B:' ) { |
| 3041 | + # Salted |
| 3042 | + list( $salt, $realHash ) = explode( ':', substr( $hash, 3 ), 2 ); |
| 3043 | + return md5( $salt.'-'.md5( $password ) ) == $realHash; |
| 3044 | + } else { |
| 3045 | + # Old-style |
| 3046 | + return self::oldCrypt( $password, $userId ) === $hash; |
| 3047 | + } |
| 3048 | + } |
3005 | 3049 | } |
Index: trunk/phase3/includes/GlobalFunctions.php |
— | — | @@ -1795,23 +1795,6 @@ |
1796 | 1796 | } |
1797 | 1797 | |
1798 | 1798 | /** |
1799 | | - * Encrypt a username/password. |
1800 | | - * |
1801 | | - * @param string $userid ID of the user |
1802 | | - * @param string $password Password of the user |
1803 | | - * @return string Hashed password |
1804 | | - */ |
1805 | | -function wfEncryptPassword( $userid, $password ) { |
1806 | | - global $wgPasswordSalt; |
1807 | | - $p = md5( $password); |
1808 | | - |
1809 | | - if($wgPasswordSalt) |
1810 | | - return md5( "{$userid}-{$p}" ); |
1811 | | - else |
1812 | | - return $p; |
1813 | | -} |
1814 | | - |
1815 | | -/** |
1816 | 1799 | * Appends to second array if $value differs from that in $default |
1817 | 1800 | */ |
1818 | 1801 | function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) { |
Index: trunk/extensions/PasswordReset/PasswordReset_body.php |
— | — | @@ -145,20 +145,20 @@ |
146 | 146 | } |
147 | 147 | |
148 | 148 | private function resetPassword( $userID, $newpass, $disableuser ) { |
| 149 | + global $wgMemc; |
149 | 150 | $dbw =& wfGetDB( DB_MASTER ); |
150 | 151 | |
| 152 | + |
| 153 | + $user = User::newFromId( $userID ); |
| 154 | + |
151 | 155 | if ( $disableuser ) { |
152 | | - $passHash = 'DISABLED'; |
| 156 | + $user->setPassword( null ); |
153 | 157 | $message = wfMsg( 'passwordreset-disablesuccess', $userID ); |
154 | 158 | } else { |
155 | | - $passHash = wfEncryptPassword( $userID, $newpass ); |
| 159 | + $user->setPassword( $newpass ); |
156 | 160 | $message = wfMsg( 'passwordreset-success', $userID ); |
157 | 161 | } |
158 | | - |
159 | | - $dbw->update( 'user', |
160 | | - array( 'user_password' => $passHash ), |
161 | | - array( 'user_id' => $userID ) |
162 | | - ); |
| 162 | + $user->saveSettings(); |
163 | 163 | return $message; |
164 | 164 | } |
165 | 165 | |
Index: trunk/extensions/Maintenance/Maintenance_body.php |
— | — | @@ -117,7 +117,9 @@ |
118 | 118 | } |
119 | 119 | $dbw = wfGetDB( DB_MASTER ); |
120 | 120 | $fname = 'ChangePassword::main'; |
121 | | - $dbw->update( 'user', array( 'user_password' => wfEncryptPassword( $user->getID(), $password ) ), array( 'user_id' => $user->getID() ), $fname ); |
| 121 | + |
| 122 | + $user->setPassword( $password ); |
| 123 | + $user->saveSettings(); |
122 | 124 | $wgOut->addWikiText( wfMsg('maintenance-success', array( $type ) ) ); |
123 | 125 | break; |
124 | 126 | case 'createAndPromote': |
Index: trunk/extensions/CentralAuth/CentralAuthUser.php |
— | — | @@ -791,6 +791,7 @@ |
792 | 792 | * @return Status |
793 | 793 | */ |
794 | 794 | public function adminUnattach( $list ) { |
| 795 | + global $wgMemc; |
795 | 796 | if ( !count( $list ) ) { |
796 | 797 | return Status::newFatal( 'centralauth-admin-none-selected' ); |
797 | 798 | } |
— | — | @@ -805,6 +806,7 @@ |
806 | 807 | $invalidCount = count( $list ) - count( $valid ); |
807 | 808 | $missingCount = 0; |
808 | 809 | $dbcw = self::getCentralDB(); |
| 810 | + $password = $this->getPassword(); |
809 | 811 | |
810 | 812 | foreach ( $valid as $wikiName ) { |
811 | 813 | # Delete the user from the central localuser table |
— | — | @@ -820,11 +822,18 @@ |
821 | 823 | continue; |
822 | 824 | } |
823 | 825 | |
824 | | - # Touch the local user row |
| 826 | + # Touch the local user row, update the password |
825 | 827 | $lb = wfGetLB( $wikiName ); |
826 | 828 | $dblw = $lb->getConnection( DB_MASTER, array(), $wikiName ); |
827 | | - $dblw->update( 'user', array( 'user_touched' => wfTimestampNow() ), |
828 | | - array( 'user_name' => $this->mName ), __METHOD__ ); |
| 829 | + $dblw->update( 'user', |
| 830 | + array( |
| 831 | + 'user_touched' => wfTimestampNow(), |
| 832 | + 'user_password' => $password |
| 833 | + ), array( 'user_name' => $this->mName ), __METHOD__ |
| 834 | + ); |
| 835 | + $id = $dblw->selectField( 'user', 'user_id', array( 'user_name' => $this->mName ), __METHOD__ ); |
| 836 | + $wgMemc->delete( "$wikiName:user:id:$id" ); |
| 837 | + |
829 | 838 | $lb->reuseConnection( $dblw ); |
830 | 839 | |
831 | 840 | $status->successCount++; |
— | — | @@ -843,18 +852,40 @@ |
844 | 853 | * Delete a global account |
845 | 854 | */ |
846 | 855 | function adminDelete() { |
| 856 | + global $wgMemc; |
847 | 857 | wfDebugLog( 'CentralAuth', "Deleting global account for user {$this->mName}" ); |
848 | | - $dbw = self::getCentralDB(); |
849 | | - $dbw->begin(); |
| 858 | + $centralDB = self::getCentralDB(); |
| 859 | + |
| 860 | + # Synchronise passwords |
| 861 | + $password = $this->getPassword(); |
| 862 | + $localUserRes = $centralDB->select( 'localuser', '*', |
| 863 | + array( 'lu_name' => $this->mName ), __METHOD__ ); |
| 864 | + $name = $this->getName(); |
| 865 | + foreach ( $localUserRes as $localUserRow ) { |
| 866 | + $wiki = $localUserRow->lu_wiki; |
| 867 | + wfDebug( __METHOD__.": Fixing password on $wiki\n" ); |
| 868 | + $lb = wfGetLB( $wiki ); |
| 869 | + $localDB = $lb->getConnection( DB_MASTER, array(), $wiki ); |
| 870 | + $localDB->update( 'user', |
| 871 | + array( 'user_password' => $password ), |
| 872 | + array( 'user_name' => $name ), |
| 873 | + __METHOD__ |
| 874 | + ); |
| 875 | + $id = $localDB->selectField( 'user', 'user_id', array( 'user_name' => $this->mName ), __METHOD__ ); |
| 876 | + $wgMemc->delete( "$wiki:user:id:$id" ); |
| 877 | + $lb->reuseConnection( $localDB ); |
| 878 | + } |
| 879 | + |
| 880 | + $centralDB->begin(); |
850 | 881 | # Delete and lock the globaluser row |
851 | | - $dbw->delete( 'globaluser', array( 'gu_name' => $this->mName ), __METHOD__ ); |
852 | | - if ( !$dbw->affectedRows() ) { |
853 | | - $dbw->commit(); |
| 882 | + $centralDB->delete( 'globaluser', array( 'gu_name' => $this->mName ), __METHOD__ ); |
| 883 | + if ( !$centralDB->affectedRows() ) { |
| 884 | + $centralDB->commit(); |
854 | 885 | return Status::newFatal( 'centralauth-admin-delete-nonexistent', $this->mName ); |
855 | 886 | } |
856 | 887 | # Delete the localuser rows |
857 | | - $dbw->delete( 'localuser', array( 'lu_name' => $this->mName ), __METHOD__ ); |
858 | | - $dbw->commit(); |
| 888 | + $centralDB->delete( 'localuser', array( 'lu_name' => $this->mName ), __METHOD__ ); |
| 889 | + $centralDB->commit(); |
859 | 890 | |
860 | 891 | $this->invalidateCache(); |
861 | 892 | |
— | — | @@ -1043,19 +1074,15 @@ |
1044 | 1075 | * @return bool true on match. |
1045 | 1076 | */ |
1046 | 1077 | protected function matchHash( $plaintext, $salt, $encrypted ) { |
1047 | | - $hash = wfEncryptPassword( $salt, $plaintext ); |
1048 | | - if( $encrypted === $hash ) { |
| 1078 | + if( User::comparePasswords( $encrypted, $plaintext, $salt ) ) { |
1049 | 1079 | return true; |
1050 | 1080 | } elseif( function_exists( 'iconv' ) ) { |
1051 | 1081 | // Some wikis were converted from ISO 8859-1 to UTF-8; |
1052 | 1082 | // retained hashes may contain non-latin chars. |
1053 | | - $latin = iconv( 'UTF-8', 'WINDOWS-1252//TRANSLIT', $plaintext ); |
1054 | | - $latinHash = wfEncryptPassword( $salt, $latin ); |
1055 | | - if( $encrypted === $latinHash ) { |
| 1083 | + $latin1 = iconv( 'UTF-8', 'WINDOWS-1252//TRANSLIT', $plaintext ); |
| 1084 | + if( User::comparePasswords( $encrypted, $latin1, $salt ) ) { |
1056 | 1085 | return true; |
1057 | 1086 | } |
1058 | | - } else { |
1059 | | - $latinHash = null; |
1060 | 1087 | } |
1061 | 1088 | return false; |
1062 | 1089 | } |
— | — | @@ -1405,9 +1432,7 @@ |
1406 | 1433 | * @return array of strings, salt and hash |
1407 | 1434 | */ |
1408 | 1435 | protected function saltedPassword( $password ) { |
1409 | | - $salt = mt_rand( 0, 1000000 ); |
1410 | | - $hash = wfEncryptPassword( $salt, $password ); |
1411 | | - return array( $salt, $hash ); |
| 1436 | + return array( '', User::crypt( $password ) ); |
1412 | 1437 | } |
1413 | 1438 | |
1414 | 1439 | /** |
— | — | @@ -1436,10 +1461,28 @@ |
1437 | 1462 | |
1438 | 1463 | // Reset the auth token. |
1439 | 1464 | $this->resetAuthToken(); |
1440 | | - $this->setGlobalCookies(); |
| 1465 | + |
| 1466 | + // Set cookies if this is the currently logged-in user |
| 1467 | + global $wgUser; |
| 1468 | + if ( isset( $wgUser->centralAuthObj ) && $wgUser->centralAuthObj === $this ) { |
| 1469 | + $this->setGlobalCookies(); |
| 1470 | + } |
| 1471 | + |
1441 | 1472 | $this->invalidateCache(); |
1442 | 1473 | return true; |
1443 | 1474 | } |
| 1475 | + |
| 1476 | + /** |
| 1477 | + * Get the password hash. |
| 1478 | + * Automatically converts to a new-style hash |
| 1479 | + */ |
| 1480 | + function getPassword() { |
| 1481 | + $this->loadState(); |
| 1482 | + if ( substr( $this->mPassword, 0, 1 ) != ':' ) { |
| 1483 | + $this->mPassword = ':B:' . $this->mSalt . ':' . $this->mPassword; |
| 1484 | + } |
| 1485 | + return $this->mPassword; |
| 1486 | + } |
1444 | 1487 | |
1445 | 1488 | static function setCookie( $name, $value, $exp=0 ) { |
1446 | 1489 | global $wgCentralAuthCookiePrefix, $wgCentralAuthCookieDomain, $wgCookieSecure, |