r78118 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r78117‎ | r78118 | r78119 >
Date:08:24, 9 December 2010
Author:tstarling
Status:ok (Comments)
Tags:
Comment:
* Made the web upgrade process more friendly. Instead of saying "access denied, go away" when the user has a normal LocalSettings.php file, generate a random $wgUpgradeKey and instruct the user to insert it into their LocalSettings.php. The subsequent file modification then authenticates the session and allows the upgrade.
* When an upgrade key is entered, or a supplied upgrade key is edited into LocalSettings.php by the upgrader, fetch database settings from LocalSettings.php and AdminSettings.php for use during the upgrade. This allows the DBConnect page to be skipped, making web upgrade almost as easy to use as CLI upgrade.
* Made LocalSettingsGenerator add $wgUpgradeKey in non-commented form, for easier subsequent upgrades.
* Converted the $wgUpgradeKey check to a normal in-sequence page, called ExistingWiki. This allows the removal of related special cases from WebInstaller. The code for WebInstaller_ExistingWiki is loosely based on WebInstaller_Locked.
* Added Status::replaceMessage(), to support informative DB connection error messages from the ExistingWiki page.
* In WebInstaller::getInfoBox(), call parse() with $lineStart=true, so that line-start syntax like bullet points can work.
* Reduced the length of the generated $wgUpgradeKey from 64 to 16. This is ample for what it does, and makes it fit on the screen and not overlap with the right sidebar when when displayed by WebInstaller_ExistingWiki.
* Added $wgUpgradeKey to DefaultSettings.php.
Modified paths:
  • /trunk/phase3/includes/DefaultSettings.php (modified) (history)
  • /trunk/phase3/includes/Status.php (modified) (history)
  • /trunk/phase3/includes/installer/CoreInstaller.php (modified) (history)
  • /trunk/phase3/includes/installer/Installer.i18n.php (modified) (history)
  • /trunk/phase3/includes/installer/Installer.php (modified) (history)
  • /trunk/phase3/includes/installer/LocalSettingsGenerator.php (modified) (history)
  • /trunk/phase3/includes/installer/WebInstaller.php (modified) (history)
  • /trunk/phase3/includes/installer/WebInstallerPage.php (modified) (history)

Diff [purge]

Index: trunk/phase3/includes/Status.php
@@ -301,6 +301,23 @@
302302 }
303303
304304 /**
 305+ * If the specified source message exists, replace it with the specified
 306+ * destination message, but keep the same parameters as in the original error.
 307+ *
 308+ * Return true if the replacement was done, false otherwise.
 309+ */
 310+ function replaceMessage( $source, $dest ) {
 311+ $replaced = false;
 312+ foreach ( $this->errors as $index => $error ) {
 313+ if ( $error['message'] === $source ) {
 314+ $this->errors[$index]['message'] = $dest;
 315+ $replaced = true;
 316+ }
 317+ }
 318+ return $replaced;
 319+ }
 320+
 321+ /**
305322 * Backward compatibility function for WikiError -> Status migration
306323 *
307324 * @return String
Index: trunk/phase3/includes/installer/WebInstaller.php
@@ -47,6 +47,7 @@
4848 */
4949 public $pageSequence = array(
5050 'Language',
 51+ 'ExistingWiki',
5152 'Welcome',
5253 'DBConnect',
5354 'Upgrade',
@@ -175,15 +176,7 @@
176177 # Get the page name.
177178 $pageName = $this->request->getVal( 'page' );
178179
179 - # Check LocalSettings status
180 - $localSettings = $this->getLocalSettingsStatus();
181 -
182 - if( !$localSettings->isGood() && $this->getVar( '_LocalSettingsLocked' ) ) {
183 - $pageName = 'Locked';
184 - $pageId = false;
185 - $page = $this->getPageByName( $pageName );
186 - $page->setLocalSettingsStatus( $localSettings );
187 - } elseif ( in_array( $pageName, $this->otherPages ) ) {
 180+ if ( in_array( $pageName, $this->otherPages ) ) {
188181 # Out of sequence
189182 $pageId = false;
190183 $page = $this->getPageByName( $pageName );
@@ -620,7 +613,7 @@
621614 ) . "\n" .
622615 "</div>\n" .
623616 "<div class=\"config-info-right\">\n" .
624 - $this->parse( $text ) . "\n" .
 617+ $this->parse( $text, true ) . "\n" .
625618 "</div>\n" .
626619 "<div style=\"clear: left;\"></div>\n" .
627620 "</div>\n";
Index: trunk/phase3/includes/installer/Installer.php
@@ -213,33 +213,30 @@
214214 }
215215
216216 /**
217 - * Determine if LocalSettings exists. If it does, return an appropriate
218 - * status for whether upgrading is enabled or not.
 217+ * Determine if LocalSettings.php exists. If it does, return its variables,
 218+ * merged with those from AdminSettings.php, as an array.
219219 *
220 - * @return Status
 220+ * @return Array
221221 */
222 - public function getLocalSettingsStatus() {
 222+ public function getExistingLocalSettings() {
223223 global $IP;
224224
225 - $status = Status::newGood();
226 -
227225 wfSuppressWarnings();
228 - $ls = file_exists( "$IP/LocalSettings.php" );
 226+ $_lsExists = file_exists( "$IP/LocalSettings.php" );
229227 wfRestoreWarnings();
230228
231 - if( $ls ) {
 229+ if( $_lsExists ) {
232230 require( "$IP/includes/DefaultSettings.php" );
233 - require_once( "$IP/LocalSettings.php" );
234 - $vars = get_defined_vars();
235 - if( isset( $vars['wgUpgradeKey'] ) && $vars['wgUpgradeKey'] ) {
236 - $status->warning( 'config-localsettings-upgrade' );
237 - $this->setVar( '_UpgradeKey', $vars['wgUpgradeKey' ] );
238 - } else {
239 - $status->fatal( 'config-localsettings-noupgrade' );
 231+ require( "$IP/LocalSettings.php" );
 232+ if ( file_exists( "$IP/AdminSettings.php" ) ) {
 233+ require( "$IP/AdminSettings.php" );
240234 }
 235+ $vars = get_defined_vars();
 236+ unset( $vars['_lsExists'] );
 237+ return $vars;
 238+ } else {
 239+ return false;
241240 }
242 -
243 - return $status;
244241 }
245242
246243 /**
Index: trunk/phase3/includes/installer/CoreInstaller.php
@@ -84,8 +84,8 @@
8585 '_CCDone' => false,
8686 '_Extensions' => array(),
8787 '_MemCachedServers' => '',
88 - '_LocalSettingsLocked' => true,
89 - '_UpgradeKey' => '',
 88+ '_UpgradeKeySupplied' => false,
 89+ '_ExistingDBSettings' => false,
9090 );
9191
9292 /**
@@ -381,7 +381,7 @@
382382 *
383383 * @return Status
384384 */
385 - protected function generateSecret( $secretName ) {
 385+ protected function generateSecret( $secretName, $length = 64 ) {
386386 if ( wfIsWindows() ) {
387387 $file = null;
388388 } else {
@@ -393,12 +393,12 @@
394394 $status = Status::newGood();
395395
396396 if ( $file ) {
397 - $secretKey = bin2hex( fread( $file, 32 ) );
 397+ $secretKey = bin2hex( fread( $file, $length / 2 ) );
398398 fclose( $file );
399399 } else {
400400 $secretKey = '';
401401
402 - for ( $i=0; $i<8; $i++ ) {
 402+ for ( $i = 0; $i < $length / 8; $i++ ) {
403403 $secretKey .= dechex( mt_rand( 0, 0x7fffffff ) );
404404 }
405405
@@ -411,13 +411,15 @@
412412 }
413413
414414 /**
415 - * Generate a default $wgUpradeKey, Will warn if we had to use
 415+ * Generate a default $wgUpgradeKey. Will warn if we had to use
416416 * mt_rand() instead of /dev/urandom
417417 *
418418 * @return Status
419419 */
420 - protected function generateUpgradeKey() {
421 - return $this->generateSecret( 'wgUpgradeKey' );
 420+ public function generateUpgradeKey() {
 421+ if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) {
 422+ return $this->generateSecret( 'wgUpgradeKey', 16 );
 423+ }
422424 }
423425
424426 /**
Index: trunk/phase3/includes/installer/Installer.i18n.php
@@ -15,14 +15,22 @@
1616 'config-desc' => 'The installer for MediaWiki',
1717 'config-title' => 'MediaWiki $1 installation',
1818 'config-information' => 'Information',
19 - 'config-localsettings-upgrade' => "'''Warning''': A <code>LocalSettings.php</code> file has been detected.
20 -Your software is able to upgrade.
21 -Please fill in the value of <code>\$wgUpgradeKey</code> in the box.",
 19+ 'config-localsettings-upgrade' => "A <code>LocalSettings.php</code> file has been detected.
 20+To upgrade this installation, please enter the value of <code>\$wgUpgradeKey</code> in the box below.
 21+You will find it in LocalSettings.php.",
2222 'config-localsettings-key' => 'Upgrade key:',
23 - 'config-localsettings-badkey' => 'The key you provided is incorrect',
24 - 'config-localsettings-noupgrade' => "'''Error''': A <code>LocalSettings.php</code> file has been detected.
25 -Your software is not able to upgrade at this time.
26 -The installer has been disabled for security reasons.",
 23+ 'config-localsettings-badkey' => 'The key you provided is incorrect.',
 24+ 'config-upgrade-key-missing' => 'An existing installation of MediaWiki has been detected.
 25+To upgrade this installation, please put the following line at the bottom of your LocalSettings.php:
 26+
 27+$1
 28+',
 29+ 'config-localsettings-incomplete' => 'The existing LocalSettings.php appears to be incomplete.
 30+The $1 variable is not set.
 31+Please change LocalSettings.php so that this variable is set, and click "Continue".',
 32+ 'config-localsettings-connection-error' => 'An error was encountered when connecting to the database using the settings specified in LocalSettings.php or AdminSettings.php. Please fix these settings and try again.
 33+
 34+$1',
2735 'config-session-error' => 'Error starting session: $1',
2836 'config-session-expired' => 'Your session data seems to have expired.
2937 Sessions are configured for a lifetime of $1.
@@ -51,7 +59,7 @@
5260 'config-page-releasenotes' => 'Release notes',
5361 'config-page-copying' => 'Copying',
5462 'config-page-upgradedoc' => 'Upgrading',
55 - 'config-page-locked' => 'Permission denied',
 63+ 'config-page-existingwiki' => 'Existing wiki',
5664 'config-help-restart' => 'Do you want to clear all saved data that you have entered and restart the installation process?',
5765 'config-restart' => 'Yes, restart it',
5866 'config-welcome' => "=== Environmental checks ===
Index: trunk/phase3/includes/installer/LocalSettingsGenerator.php
@@ -282,7 +282,7 @@
283283
284284 # Site upgrade key. Must be set to a string (default provided) to turn on the
285285 # web installer while LocalSettings.php is in place
286 -#\$wgUpgradeKey = \"{$this->values['wgUpgradeKey']}\";
 286+\$wgUpgradeKey = \"{$this->values['wgUpgradeKey']}\";
287287
288288 ## Default skin: you can change the default skin. Use the internal symbolic
289289 ## names, ie 'standard', 'nostalgia', 'cologneblue', 'monobook', 'vector':
Index: trunk/phase3/includes/installer/WebInstallerPage.php
@@ -116,56 +116,6 @@
117117 }
118118 }
119119
120 -class WebInstaller_Locked extends WebInstallerPage {
121 - // The status of Installer::getLocalSettingsStatus()
122 - private $status;
123 -
124 - public function setLocalSettingsStatus( Status $s ) {
125 - $this->status = $s;
126 - }
127 -
128 - protected function getId() {
129 - return 0;
130 - }
131 -
132 - public function execute() {
133 - $r = $this->parent->request;
134 - if( !$r->wasPosted() || !$this->status->isOK() ) {
135 - $this->display();
136 - return 'output';
137 - } else {
138 - $key = $r->getText( 'config_wpUpgradeKey' );
139 - if( !$key || $key !== $this->getVar( '_UpgradeKey' ) ) {
140 - $this->parent->showError( 'config-localsettings-badkey' );
141 - $this->display();
142 - return 'output';
143 - } else {
144 - $this->setVar( '_LocalSettingsLocked', false );
145 - return 'continue';
146 - }
147 - }
148 - }
149 -
150 - /**
151 - * Display stuff to the end user
152 - */
153 - private function display() {
154 - $this->startForm();
155 - $this->parent->showStatusBox( $this->status );
156 - $continue = false;
157 - if( $this->status->isOK() && !$this->status->isGood() ) {
158 - $this->addHTML( "<br />" .
159 - $this->parent->getTextBox( array(
160 - 'var' => 'wpUpgradeKey',
161 - 'label' => 'config-localsettings-key',
162 - ) )
163 - );
164 - $continue = 'continue';
165 - }
166 - $this->endForm( $continue );
167 - }
168 -}
169 -
170120 class WebInstaller_Language extends WebInstallerPage {
171121
172122 public function execute() {
@@ -245,6 +195,146 @@
246196
247197 }
248198
 199+class WebInstaller_ExistingWiki extends WebInstallerPage {
 200+ public function execute() {
 201+ // If there is no LocalSettings.php, continue to the installer welcome page
 202+ $vars = $this->parent->getExistingLocalSettings();
 203+ if ( !$vars ) {
 204+ return 'skip';
 205+ }
 206+
 207+ // Check if the upgrade key supplied to the user has appeared in LocalSettings.php
 208+ if ( $vars['wgUpgradeKey'] !== false
 209+ && $this->getVar( '_UpgradeKeySupplied' )
 210+ && $this->getVar( 'wgUpgradeKey' ) === $vars['wgUpgradeKey'] )
 211+ {
 212+ // It's there, so the user is authorized
 213+ $status = $this->handleExistingUpgrade( $vars );
 214+ if ( $status->isOK() ) {
 215+ return 'skip';
 216+ } else {
 217+ $this->startForm();
 218+ $this->parent->showStatusBox( $status );
 219+ $this->endForm( 'continue' );
 220+ return 'output';
 221+ }
 222+ return $this->handleExistingUpgrade( $vars );
 223+ }
 224+
 225+ // If there is no $wgUpgradeKey, tell the user to add one to LocalSettings.php
 226+ if ( $vars['wgUpgradeKey'] === false ) {
 227+ if ( $this->getVar( 'wgUpgradeKey', false ) === false ) {
 228+ $this->parent->generateUpgradeKey();
 229+ $this->setVar( '_UpgradeKeySupplied', true );
 230+ }
 231+ $this->startForm();
 232+ $this->addHTML( $this->parent->getInfoBox(
 233+ wfMsgNoTrans( 'config-upgrade-key-missing',
 234+ "<pre>\$wgUpgradeKey = '" . $this->getVar( 'wgUpgradeKey' ) . "';</pre>" )
 235+ ) );
 236+ $this->endForm( 'continue' );
 237+ return 'output';
 238+ }
 239+
 240+ // If there is an upgrade key, but it wasn't supplied, prompt the user to enter it
 241+
 242+ $r = $this->parent->request;
 243+ if ( $r->wasPosted() ) {
 244+ $key = $r->getText( 'config_wgUpgradeKey' );
 245+ if( !$key || $key !== $vars['wgUpgradeKey'] ) {
 246+ $this->parent->showError( 'config-localsettings-badkey' );
 247+ $this->showKeyForm();
 248+ return 'output';
 249+ }
 250+ // Key was OK
 251+ $status = $this->handleExistingUpgrade( $vars );
 252+ if ( $status->isOK() ) {
 253+ return 'continue';
 254+ } else {
 255+ $this->parent->showStatusBox( $status );
 256+ $this->showKeyForm();
 257+ return 'output';
 258+ }
 259+ } else {
 260+ $this->showKeyForm();
 261+ return 'output';
 262+ }
 263+ }
 264+
 265+ /**
 266+ * Show the "enter key" form
 267+ */
 268+ protected function showKeyForm() {
 269+ $this->startForm();
 270+ $this->addHTML(
 271+ $this->parent->getInfoBox( wfMsgNoTrans( 'config-localsettings-upgrade' ) ).
 272+ '<br />' .
 273+ $this->parent->getTextBox( array(
 274+ 'var' => 'wgUpgradeKey',
 275+ 'label' => 'config-localsettings-key',
 276+ 'attribs' => array( 'autocomplete' => 'off' ),
 277+ ) )
 278+ );
 279+ $this->endForm( 'continue' );
 280+ }
 281+
 282+ protected function importVariables( $names, $vars ) {
 283+ $status = Status::newGood();
 284+ foreach ( $names as $name ) {
 285+ if ( !isset( $vars[$name] ) ) {
 286+ $status->fatal( 'config-localsettings-incomplete', $name );
 287+ }
 288+ $this->setVar( $name, $vars[$name] );
 289+ }
 290+ return $status;
 291+ }
 292+
 293+ /**
 294+ * Initiate an upgrade of the existing database
 295+ * @param $vars Variables from LocalSettings.php and AdminSettings.php
 296+ * @return Status
 297+ */
 298+ protected function handleExistingUpgrade( $vars ) {
 299+ // Check $wgDBtype
 300+ if ( !isset( $vars['wgDBtype'] ) || !in_array( $vars['wgDBtype'], Installer::getDBTypes() ) ) {
 301+ return Status::newFatal( 'config-localsettings-connection-error', '' );
 302+ }
 303+
 304+ // Set the relevant variables from LocalSettings.php
 305+ $requiredVars = array( 'wgDBtype', 'wgDBuser', 'wgDBpassword' );
 306+ $status = $this->importVariables( $requiredVars , $vars );
 307+ $installer = $this->parent->getDBInstaller();
 308+ $status->merge( $this->importVariables( $installer->getGlobalNames(), $vars ) );
 309+ if ( !$status->isOK() ) {
 310+ return $status;
 311+ }
 312+
 313+ if ( isset( $vars['wgDBadminuser'] ) ) {
 314+ $this->setVar( '_InstallUser', $vars['wgDBadminuser'] );
 315+ } else {
 316+ $this->setVar( '_InstallUser', $vars['wgDBuser'] );
 317+ }
 318+ if ( isset( $vars['wgDBadminpassword'] ) ) {
 319+ $this->setVar( '_InstallPassword', $vars['wgDBadminpassword'] );
 320+ } else {
 321+ $this->setVar( '_InstallPassword', $vars['wgDBpassword'] );
 322+ }
 323+
 324+ // Test the database connection
 325+ $status = $installer->getConnection();
 326+ if ( !$status->isOK() ) {
 327+ // Adjust the error message to explain things correctly
 328+ $status->replaceMessage( 'config-connection-error',
 329+ 'config-localsettings-connection-error' );
 330+ return $status;
 331+ }
 332+
 333+ // All good
 334+ $this->setVar( '_ExistingDBSettings', true );
 335+ return $status;
 336+ }
 337+}
 338+
249339 class WebInstaller_Welcome extends WebInstallerPage {
250340
251341 public function execute() {
@@ -268,6 +358,10 @@
269359 class WebInstaller_DBConnect extends WebInstallerPage {
270360
271361 public function execute() {
 362+ if ( $this->getVar( '_ExistingDBSettings' ) ) {
 363+ return 'skip';
 364+ }
 365+
272366 $r = $this->parent->request;
273367 if ( $r->wasPosted() ) {
274368 $status = $this->submit();
Index: trunk/phase3/includes/DefaultSettings.php
@@ -5233,6 +5233,10 @@
52345234 $wgSeleniumTestConfigs = array();
52355235 $wgSeleniumConfigFile = null;
52365236
 5237+/**
 5238+ * Set this to a random string to allow web-based upgrades
 5239+ */
 5240+$wgUpgradeKey = false;
52375241
52385242
52395243 /**

Follow-up revisions

RevisionCommit summaryAuthorDate
r78137Followup r78118: $wgUpgradeKey was already in DefaultSettings, use Tim's bett...demon20:36, 9 December 2010
r78165* Hide the "back" buttons on the completion pages, they are potentially confu...tstarling03:02, 10 December 2010
r78231Remove that ugly unset( $vars['_lsExists'] );...platonides22:54, 11 December 2010
r784371.17: Merge tagged revisions from trunk: r77878, r77981, r77982, r77994, r780...catrope14:14, 15 December 2010

Comments

#Comment by 😂 (talk | contribs)   20:59, 9 December 2010

When I wrote the $wgUpgradeKey feature, I purposefully didn't read the rest of LocalSettings.php. Maybe I was being overly paranoid about a user having an easy-to-guess $wgUpgradeKey.

#Comment by Tim Starling (talk | contribs)   22:54, 9 December 2010

Providing a secure $wgUpgradeKey value for users of pre-1.17 wikis means that the chance that a user will make up a weak one is significantly reduced. We can probably reduce it further by changing the doc comment in DefaultSettings.php to indicate that it should only be set to the key provided by the installer.

Also, if you enter the $wgUpgradeKey now, all you can do is upgrade, you can't install new wikis. We should make sure that that's as harmless as it can be.

Status & tagging log