r35923 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r35922‎ | r35923 | r35924 >
Date:12:58, 5 June 2008
Author:tstarling
Status:old
Tags:
Comment:
* Changed password hash format, see wikitech-l
* Made the PasswordReset and Maintenance extensions, and maintenance/changePassword.php work with CentralAuth, by calling User::setPassword() instead of updating the database directly. They work now even if you use an object cache.
* Don't automatically log in as the user in question when CentralAuthUser::setPassword() is called, it's kind of uncool when an administrator is setting the password of another user.
* Fix bug 14330 by setting the local passwords on demerge
Modified paths:
  • /trunk/extensions/CentralAuth/CentralAuthUser.php (modified) (history)
  • /trunk/extensions/Maintenance/Maintenance_body.php (modified) (history)
  • /trunk/extensions/PasswordReset/PasswordReset_body.php (modified) (history)
  • /trunk/phase3/includes/GlobalFunctions.php (modified) (history)
  • /trunk/phase3/includes/User.php (modified) (history)
  • /trunk/phase3/maintenance/changePassword.php (modified) (history)
  • /trunk/phase3/maintenance/updaters.inc (modified) (history)

Diff [purge]

Index: trunk/phase3/maintenance/changePassword.php
@@ -52,14 +52,7 @@
5353 function main() {
5454 $fname = 'ChangePassword::main';
5555
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();
6558 }
6659 }
Index: trunk/phase3/maintenance/updaters.inc
@@ -142,6 +142,7 @@
143143 array( 'check_bin', 'protected_titles', 'pt_title', 'patch-pt_title-encoding.sql', ),
144144 array( 'maybe_do_profiling_memory_update' ),
145145 array( 'do_filearchive_indices_update' ),
 146+ array( 'update_password_format' ),
146147 );
147148
148149
@@ -1195,6 +1196,28 @@
11961197 populate_rev_parent_id( $wgDatabase );
11971198 }
11981199
 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+
11991222 function
12001223 pg_describe_table($table)
12011224 {
@@ -1690,3 +1713,4 @@
16911714
16921715 return;
16931716 }
 1717+
Index: trunk/phase3/includes/User.php
@@ -1516,18 +1516,6 @@
15171517 }
15181518
15191519 /**
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 - /**
15321520 * Set the password and reset the random token
15331521 * Calls through to authentication plugin if necessary;
15341522 * will have no effect if the auth plugin refuses to
@@ -1579,7 +1567,7 @@
15801568 // Save an invalid hash...
15811569 $this->mPassword = '';
15821570 } else {
1583 - $this->mPassword = $this->encryptPassword( $str );
 1571+ $this->mPassword = self::crypt( $str );
15841572 }
15851573 $this->mNewpassword = '';
15861574 $this->mNewpassTime = null;
@@ -1623,7 +1611,7 @@
16241612 */
16251613 function setNewpassword( $str, $throttle = true ) {
16261614 $this->load();
1627 - $this->mNewpassword = $this->encryptPassword( $str );
 1615+ $this->mNewpassword = self::crypt( $str );
16281616 if ( $throttle ) {
16291617 $this->mNewpassTime = wfTimestampNow();
16301618 }
@@ -2505,14 +2493,13 @@
25062494 /* Auth plugin doesn't allow local authentication for this user name */
25072495 return false;
25082496 }
2509 - $ep = $this->encryptPassword( $password );
2510 - if ( 0 == strcmp( $ep, $this->mPassword ) ) {
 2497+ if ( self::comparePasswords( $this->mPassword, $password, $this->mId ) ) {
25112498 return true;
25122499 } elseif ( function_exists( 'iconv' ) ) {
25132500 # Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
25142501 # 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 ) ) {
25172504 return true;
25182505 }
25192506 }
@@ -2525,8 +2512,7 @@
25262513 * @return bool
25272514 */
25282515 function checkTemporaryPassword( $plaintext ) {
2529 - $hash = $this->encryptPassword( $plaintext );
2530 - return $hash === $this->mNewpassword;
 2516+ return self::comparePasswords( $this->mNewpassword, $plaintext, $this->getId() );
25312517 }
25322518
25332519 /**
@@ -3001,4 +2987,62 @@
30022988 ? $right
30032989 : $name;
30042990 }
 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+ }
30053049 }
Index: trunk/phase3/includes/GlobalFunctions.php
@@ -1795,23 +1795,6 @@
17961796 }
17971797
17981798 /**
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 -/**
18161799 * Appends to second array if $value differs from that in $default
18171800 */
18181801 function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
Index: trunk/extensions/PasswordReset/PasswordReset_body.php
@@ -145,20 +145,20 @@
146146 }
147147
148148 private function resetPassword( $userID, $newpass, $disableuser ) {
 149+ global $wgMemc;
149150 $dbw =& wfGetDB( DB_MASTER );
150151
 152+
 153+ $user = User::newFromId( $userID );
 154+
151155 if ( $disableuser ) {
152 - $passHash = 'DISABLED';
 156+ $user->setPassword( null );
153157 $message = wfMsg( 'passwordreset-disablesuccess', $userID );
154158 } else {
155 - $passHash = wfEncryptPassword( $userID, $newpass );
 159+ $user->setPassword( $newpass );
156160 $message = wfMsg( 'passwordreset-success', $userID );
157161 }
158 -
159 - $dbw->update( 'user',
160 - array( 'user_password' => $passHash ),
161 - array( 'user_id' => $userID )
162 - );
 162+ $user->saveSettings();
163163 return $message;
164164 }
165165
Index: trunk/extensions/Maintenance/Maintenance_body.php
@@ -117,7 +117,9 @@
118118 }
119119 $dbw = wfGetDB( DB_MASTER );
120120 $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();
122124 $wgOut->addWikiText( wfMsg('maintenance-success', array( $type ) ) );
123125 break;
124126 case 'createAndPromote':
Index: trunk/extensions/CentralAuth/CentralAuthUser.php
@@ -791,6 +791,7 @@
792792 * @return Status
793793 */
794794 public function adminUnattach( $list ) {
 795+ global $wgMemc;
795796 if ( !count( $list ) ) {
796797 return Status::newFatal( 'centralauth-admin-none-selected' );
797798 }
@@ -805,6 +806,7 @@
806807 $invalidCount = count( $list ) - count( $valid );
807808 $missingCount = 0;
808809 $dbcw = self::getCentralDB();
 810+ $password = $this->getPassword();
809811
810812 foreach ( $valid as $wikiName ) {
811813 # Delete the user from the central localuser table
@@ -820,11 +822,18 @@
821823 continue;
822824 }
823825
824 - # Touch the local user row
 826+ # Touch the local user row, update the password
825827 $lb = wfGetLB( $wikiName );
826828 $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+
829838 $lb->reuseConnection( $dblw );
830839
831840 $status->successCount++;
@@ -843,18 +852,40 @@
844853 * Delete a global account
845854 */
846855 function adminDelete() {
 856+ global $wgMemc;
847857 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();
850881 # 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();
854885 return Status::newFatal( 'centralauth-admin-delete-nonexistent', $this->mName );
855886 }
856887 # 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();
859890
860891 $this->invalidateCache();
861892
@@ -1043,19 +1074,15 @@
10441075 * @return bool true on match.
10451076 */
10461077 protected function matchHash( $plaintext, $salt, $encrypted ) {
1047 - $hash = wfEncryptPassword( $salt, $plaintext );
1048 - if( $encrypted === $hash ) {
 1078+ if( User::comparePasswords( $encrypted, $plaintext, $salt ) ) {
10491079 return true;
10501080 } elseif( function_exists( 'iconv' ) ) {
10511081 // Some wikis were converted from ISO 8859-1 to UTF-8;
10521082 // 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 ) ) {
10561085 return true;
10571086 }
1058 - } else {
1059 - $latinHash = null;
10601087 }
10611088 return false;
10621089 }
@@ -1405,9 +1432,7 @@
14061433 * @return array of strings, salt and hash
14071434 */
14081435 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 ) );
14121437 }
14131438
14141439 /**
@@ -1436,10 +1461,28 @@
14371462
14381463 // Reset the auth token.
14391464 $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+
14411472 $this->invalidateCache();
14421473 return true;
14431474 }
 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+ }
14441487
14451488 static function setCookie( $name, $value, $exp=0 ) {
14461489 global $wgCentralAuthCookiePrefix, $wgCentralAuthCookieDomain, $wgCookieSecure,

Follow-up revisions

RevisionCommit summaryAuthorDate
r36208Re-add wfEncryptPassword() (removed in r35923 in favor of User::crypt() and U...catrope22:20, 11 June 2008

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r35880* Change user local password to a global one on login and account autocreatio...vasilievvv20:29, 4 June 2008

Status & tagging log