MediaWiki
REL1_22
|
00001 <?php 00030 class PostgresInstaller extends DatabaseInstaller { 00031 00032 protected $globalNames = array( 00033 'wgDBserver', 00034 'wgDBport', 00035 'wgDBname', 00036 'wgDBuser', 00037 'wgDBpassword', 00038 'wgDBmwschema', 00039 ); 00040 00041 protected $internalDefaults = array( 00042 '_InstallUser' => 'postgres', 00043 ); 00044 00045 public $minimumVersion = '8.3'; 00046 public $maxRoleSearchDepth = 5; 00047 00048 protected $pgConns = array(); 00049 00050 function getName() { 00051 return 'postgres'; 00052 } 00053 00054 public function isCompiled() { 00055 return self::checkExtension( 'pgsql' ); 00056 } 00057 00058 function getConnectForm() { 00059 return $this->getTextBox( 00060 'wgDBserver', 00061 'config-db-host', 00062 array(), 00063 $this->parent->getHelpBox( 'config-db-host-help' ) 00064 ) . 00065 $this->getTextBox( 'wgDBport', 'config-db-port' ) . 00066 Html::openElement( 'fieldset' ) . 00067 Html::element( 'legend', array(), wfMessage( 'config-db-wiki-settings' )->text() ) . 00068 $this->getTextBox( 00069 'wgDBname', 00070 'config-db-name', 00071 array(), 00072 $this->parent->getHelpBox( 'config-db-name-help' ) 00073 ) . 00074 $this->getTextBox( 00075 'wgDBmwschema', 00076 'config-db-schema', 00077 array(), 00078 $this->parent->getHelpBox( 'config-db-schema-help' ) 00079 ) . 00080 Html::closeElement( 'fieldset' ) . 00081 $this->getInstallUserBox(); 00082 } 00083 00084 function submitConnectForm() { 00085 // Get variables from the request 00086 $newValues = $this->setVarsFromRequest( array( 'wgDBserver', 'wgDBport', 00087 'wgDBname', 'wgDBmwschema' ) ); 00088 00089 // Validate them 00090 $status = Status::newGood(); 00091 if ( !strlen( $newValues['wgDBname'] ) ) { 00092 $status->fatal( 'config-missing-db-name' ); 00093 } elseif ( !preg_match( '/^[a-zA-Z0-9_]+$/', $newValues['wgDBname'] ) ) { 00094 $status->fatal( 'config-invalid-db-name', $newValues['wgDBname'] ); 00095 } 00096 if ( !preg_match( '/^[a-zA-Z0-9_]*$/', $newValues['wgDBmwschema'] ) ) { 00097 $status->fatal( 'config-invalid-schema', $newValues['wgDBmwschema'] ); 00098 } 00099 00100 // Submit user box 00101 if ( $status->isOK() ) { 00102 $status->merge( $this->submitInstallUserBox() ); 00103 } 00104 if ( !$status->isOK() ) { 00105 return $status; 00106 } 00107 00108 $status = $this->getPgConnection( 'create-db' ); 00109 if ( !$status->isOK() ) { 00110 return $status; 00111 } 00115 $conn = $status->value; 00116 00117 // Check version 00118 $version = $conn->getServerVersion(); 00119 if ( version_compare( $version, $this->minimumVersion ) < 0 ) { 00120 return Status::newFatal( 'config-postgres-old', $this->minimumVersion, $version ); 00121 } 00122 00123 $this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) ); 00124 $this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) ); 00125 00126 return Status::newGood(); 00127 } 00128 00129 public function getConnection() { 00130 $status = $this->getPgConnection( 'create-tables' ); 00131 if ( $status->isOK() ) { 00132 $this->db = $status->value; 00133 } 00134 00135 return $status; 00136 } 00137 00138 public function openConnection() { 00139 return $this->openPgConnection( 'create-tables' ); 00140 } 00141 00149 protected function openConnectionWithParams( $user, $password, $dbName ) { 00150 $status = Status::newGood(); 00151 try { 00152 $db = new DatabasePostgres( 00153 $this->getVar( 'wgDBserver' ), 00154 $user, 00155 $password, 00156 $dbName 00157 ); 00158 $status->value = $db; 00159 } catch ( DBConnectionError $e ) { 00160 $status->fatal( 'config-connection-error', $e->getMessage() ); 00161 } 00162 00163 return $status; 00164 } 00165 00171 protected function getPgConnection( $type ) { 00172 if ( isset( $this->pgConns[$type] ) ) { 00173 return Status::newGood( $this->pgConns[$type] ); 00174 } 00175 $status = $this->openPgConnection( $type ); 00176 00177 if ( $status->isOK() ) { 00181 $conn = $status->value; 00182 $conn->clearFlag( DBO_TRX ); 00183 $conn->commit( __METHOD__ ); 00184 $this->pgConns[$type] = $conn; 00185 } 00186 00187 return $status; 00188 } 00189 00216 protected function openPgConnection( $type ) { 00217 switch ( $type ) { 00218 case 'create-db': 00219 return $this->openConnectionToAnyDB( 00220 $this->getVar( '_InstallUser' ), 00221 $this->getVar( '_InstallPassword' ) ); 00222 case 'create-schema': 00223 return $this->openConnectionWithParams( 00224 $this->getVar( '_InstallUser' ), 00225 $this->getVar( '_InstallPassword' ), 00226 $this->getVar( 'wgDBname' ) ); 00227 case 'create-tables': 00228 $status = $this->openPgConnection( 'create-schema' ); 00229 if ( $status->isOK() ) { 00233 $conn = $status->value; 00234 $safeRole = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) ); 00235 $conn->query( "SET ROLE $safeRole" ); 00236 } 00237 00238 return $status; 00239 default: 00240 throw new MWException( "Invalid special connection type: \"$type\"" ); 00241 } 00242 } 00243 00244 public function openConnectionToAnyDB( $user, $password ) { 00245 $dbs = array( 00246 'template1', 00247 'postgres', 00248 ); 00249 if ( !in_array( $this->getVar( 'wgDBname' ), $dbs ) ) { 00250 array_unshift( $dbs, $this->getVar( 'wgDBname' ) ); 00251 } 00252 $conn = false; 00253 $status = Status::newGood(); 00254 foreach ( $dbs as $db ) { 00255 try { 00256 $conn = new DatabasePostgres( 00257 $this->getVar( 'wgDBserver' ), 00258 $user, 00259 $password, 00260 $db ); 00261 } catch ( DBConnectionError $error ) { 00262 $conn = false; 00263 $status->fatal( 'config-pg-test-error', $db, 00264 $error->getMessage() ); 00265 } 00266 if ( $conn !== false ) { 00267 break; 00268 } 00269 } 00270 if ( $conn !== false ) { 00271 return Status::newGood( $conn ); 00272 } else { 00273 return $status; 00274 } 00275 } 00276 00277 protected function getInstallUserPermissions() { 00278 $status = $this->getPgConnection( 'create-db' ); 00279 if ( !$status->isOK() ) { 00280 return false; 00281 } 00285 $conn = $status->value; 00286 $superuser = $this->getVar( '_InstallUser' ); 00287 00288 $row = $conn->selectRow( '"pg_catalog"."pg_roles"', '*', 00289 array( 'rolname' => $superuser ), __METHOD__ ); 00290 00291 return $row; 00292 } 00293 00294 protected function canCreateAccounts() { 00295 $perms = $this->getInstallUserPermissions(); 00296 if ( !$perms ) { 00297 return false; 00298 } 00299 00300 return $perms->rolsuper === 't' || $perms->rolcreaterole === 't'; 00301 } 00302 00303 protected function isSuperUser() { 00304 $perms = $this->getInstallUserPermissions(); 00305 if ( !$perms ) { 00306 return false; 00307 } 00308 00309 return $perms->rolsuper === 't'; 00310 } 00311 00312 public function getSettingsForm() { 00313 if ( $this->canCreateAccounts() ) { 00314 $noCreateMsg = false; 00315 } else { 00316 $noCreateMsg = 'config-db-web-no-create-privs'; 00317 } 00318 $s = $this->getWebUserBox( $noCreateMsg ); 00319 00320 return $s; 00321 } 00322 00323 public function submitSettingsForm() { 00324 $status = $this->submitWebUserBox(); 00325 if ( !$status->isOK() ) { 00326 return $status; 00327 } 00328 00329 $same = $this->getVar( 'wgDBuser' ) === $this->getVar( '_InstallUser' ); 00330 00331 if ( $same ) { 00332 $exists = true; 00333 } else { 00334 // Check if the web user exists 00335 // Connect to the database with the install user 00336 $status = $this->getPgConnection( 'create-db' ); 00337 if ( !$status->isOK() ) { 00338 return $status; 00339 } 00340 $exists = $status->value->roleExists( $this->getVar( 'wgDBuser' ) ); 00341 } 00342 00343 // Validate the create checkbox 00344 if ( $this->canCreateAccounts() && !$same && !$exists ) { 00345 $create = $this->getVar( '_CreateDBAccount' ); 00346 } else { 00347 $this->setVar( '_CreateDBAccount', false ); 00348 $create = false; 00349 } 00350 00351 if ( !$create && !$exists ) { 00352 if ( $this->canCreateAccounts() ) { 00353 $msg = 'config-install-user-missing-create'; 00354 } else { 00355 $msg = 'config-install-user-missing'; 00356 } 00357 00358 return Status::newFatal( $msg, $this->getVar( 'wgDBuser' ) ); 00359 } 00360 00361 if ( !$exists ) { 00362 // No more checks to do 00363 return Status::newGood(); 00364 } 00365 00366 // Existing web account. Test the connection. 00367 $status = $this->openConnectionToAnyDB( 00368 $this->getVar( 'wgDBuser' ), 00369 $this->getVar( 'wgDBpassword' ) ); 00370 if ( !$status->isOK() ) { 00371 return $status; 00372 } 00373 00374 // The web user is conventionally the table owner in PostgreSQL 00375 // installations. Make sure the install user is able to create 00376 // objects on behalf of the web user. 00377 if ( $same || $this->canCreateObjectsForWebUser() ) { 00378 return Status::newGood(); 00379 } else { 00380 return Status::newFatal( 'config-pg-not-in-role' ); 00381 } 00382 } 00383 00389 protected function canCreateObjectsForWebUser() { 00390 if ( $this->isSuperUser() ) { 00391 return true; 00392 } 00393 00394 $status = $this->getPgConnection( 'create-db' ); 00395 if ( !$status->isOK() ) { 00396 return false; 00397 } 00398 $conn = $status->value; 00399 $installerId = $conn->selectField( '"pg_catalog"."pg_roles"', 'oid', 00400 array( 'rolname' => $this->getVar( '_InstallUser' ) ), __METHOD__ ); 00401 $webId = $conn->selectField( '"pg_catalog"."pg_roles"', 'oid', 00402 array( 'rolname' => $this->getVar( 'wgDBuser' ) ), __METHOD__ ); 00403 00404 return $this->isRoleMember( $conn, $installerId, $webId, $this->maxRoleSearchDepth ); 00405 } 00406 00415 protected function isRoleMember( $conn, $targetMember, $group, $maxDepth ) { 00416 if ( $targetMember === $group ) { 00417 // A role is always a member of itself 00418 return true; 00419 } 00420 // Get all members of the given group 00421 $res = $conn->select( '"pg_catalog"."pg_auth_members"', array( 'member' ), 00422 array( 'roleid' => $group ), __METHOD__ ); 00423 foreach ( $res as $row ) { 00424 if ( $row->member == $targetMember ) { 00425 // Found target member 00426 return true; 00427 } 00428 // Recursively search each member of the group to see if the target 00429 // is a member of it, up to the given maximum depth. 00430 if ( $maxDepth > 0 ) { 00431 if ( $this->isRoleMember( $conn, $targetMember, $row->member, $maxDepth - 1 ) ) { 00432 // Found member of member 00433 return true; 00434 } 00435 } 00436 } 00437 00438 return false; 00439 } 00440 00441 public function preInstall() { 00442 $createDbAccount = array( 00443 'name' => 'user', 00444 'callback' => array( $this, 'setupUser' ), 00445 ); 00446 $commitCB = array( 00447 'name' => 'pg-commit', 00448 'callback' => array( $this, 'commitChanges' ), 00449 ); 00450 $plpgCB = array( 00451 'name' => 'pg-plpgsql', 00452 'callback' => array( $this, 'setupPLpgSQL' ), 00453 ); 00454 $schemaCB = array( 00455 'name' => 'schema', 00456 'callback' => array( $this, 'setupSchema' ) 00457 ); 00458 00459 if ( $this->getVar( '_CreateDBAccount' ) ) { 00460 $this->parent->addInstallStep( $createDbAccount, 'database' ); 00461 } 00462 $this->parent->addInstallStep( $commitCB, 'interwiki' ); 00463 $this->parent->addInstallStep( $plpgCB, 'database' ); 00464 $this->parent->addInstallStep( $schemaCB, 'database' ); 00465 } 00466 00467 function setupDatabase() { 00468 $status = $this->getPgConnection( 'create-db' ); 00469 if ( !$status->isOK() ) { 00470 return $status; 00471 } 00472 $conn = $status->value; 00473 00474 $dbName = $this->getVar( 'wgDBname' ); 00475 00476 $exists = $conn->selectField( '"pg_catalog"."pg_database"', '1', 00477 array( 'datname' => $dbName ), __METHOD__ ); 00478 if ( !$exists ) { 00479 $safedb = $conn->addIdentifierQuotes( $dbName ); 00480 $conn->query( "CREATE DATABASE $safedb", __METHOD__ ); 00481 } 00482 00483 return Status::newGood(); 00484 } 00485 00486 function setupSchema() { 00487 // Get a connection to the target database 00488 $status = $this->getPgConnection( 'create-schema' ); 00489 if ( !$status->isOK() ) { 00490 return $status; 00491 } 00492 $conn = $status->value; 00493 00494 // Create the schema if necessary 00495 $schema = $this->getVar( 'wgDBmwschema' ); 00496 $safeschema = $conn->addIdentifierQuotes( $schema ); 00497 $safeuser = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) ); 00498 if ( !$conn->schemaExists( $schema ) ) { 00499 try { 00500 $conn->query( "CREATE SCHEMA $safeschema AUTHORIZATION $safeuser" ); 00501 } catch ( DBQueryError $e ) { 00502 return Status::newFatal( 'config-install-pg-schema-failed', 00503 $this->getVar( '_InstallUser' ), $schema ); 00504 } 00505 } 00506 00507 // Select the new schema in the current connection 00508 $conn->determineCoreSchema( $schema ); 00509 00510 return Status::newGood(); 00511 } 00512 00513 function commitChanges() { 00514 $this->db->commit( __METHOD__ ); 00515 00516 return Status::newGood(); 00517 } 00518 00519 function setupUser() { 00520 if ( !$this->getVar( '_CreateDBAccount' ) ) { 00521 return Status::newGood(); 00522 } 00523 00524 $status = $this->getPgConnection( 'create-db' ); 00525 if ( !$status->isOK() ) { 00526 return $status; 00527 } 00528 $conn = $status->value; 00529 00530 $safeuser = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) ); 00531 $safepass = $conn->addQuotes( $this->getVar( 'wgDBpassword' ) ); 00532 00533 // Check if the user already exists 00534 $userExists = $conn->roleExists( $this->getVar( 'wgDBuser' ) ); 00535 if ( !$userExists ) { 00536 // Create the user 00537 try { 00538 $sql = "CREATE ROLE $safeuser NOCREATEDB LOGIN PASSWORD $safepass"; 00539 00540 // If the install user is not a superuser, we need to make the install 00541 // user a member of the new user's group, so that the install user will 00542 // be able to create a schema and other objects on behalf of the new user. 00543 if ( !$this->isSuperUser() ) { 00544 $sql .= ' ROLE' . $conn->addIdentifierQuotes( $this->getVar( '_InstallUser' ) ); 00545 } 00546 00547 $conn->query( $sql, __METHOD__ ); 00548 } catch ( DBQueryError $e ) { 00549 return Status::newFatal( 'config-install-user-create-failed', 00550 $this->getVar( 'wgDBuser' ), $e->getMessage() ); 00551 } 00552 } 00553 00554 return Status::newGood(); 00555 } 00556 00557 function getLocalSettings() { 00558 $port = $this->getVar( 'wgDBport' ); 00559 $schema = $this->getVar( 'wgDBmwschema' ); 00560 00561 return "# Postgres specific settings 00562 \$wgDBport = \"{$port}\"; 00563 \$wgDBmwschema = \"{$schema}\";"; 00564 } 00565 00566 public function preUpgrade() { 00567 global $wgDBuser, $wgDBpassword; 00568 00569 # Normal user and password are selected after this step, so for now 00570 # just copy these two 00571 $wgDBuser = $this->getVar( '_InstallUser' ); 00572 $wgDBpassword = $this->getVar( '_InstallPassword' ); 00573 } 00574 00575 public function createTables() { 00576 $schema = $this->getVar( 'wgDBmwschema' ); 00577 00578 $status = $this->getConnection(); 00579 if ( !$status->isOK() ) { 00580 return $status; 00581 } 00582 00586 $conn = $status->value; 00587 00588 if ( $conn->tableExists( 'archive' ) ) { 00589 $status->warning( 'config-install-tables-exist' ); 00590 $this->enableLB(); 00591 00592 return $status; 00593 } 00594 00595 $conn->begin( __METHOD__ ); 00596 00597 if ( !$conn->schemaExists( $schema ) ) { 00598 $status->fatal( 'config-install-pg-schema-not-exist' ); 00599 00600 return $status; 00601 } 00602 $error = $conn->sourceFile( $conn->getSchemaPath() ); 00603 if ( $error !== true ) { 00604 $conn->reportQueryError( $error, 0, '', __METHOD__ ); 00605 $conn->rollback( __METHOD__ ); 00606 $status->fatal( 'config-install-tables-failed', $error ); 00607 } else { 00608 $conn->commit( __METHOD__ ); 00609 } 00610 // Resume normal operations 00611 if ( $status->isOk() ) { 00612 $this->enableLB(); 00613 } 00614 00615 return $status; 00616 } 00617 00618 public function setupPLpgSQL() { 00619 // Connect as the install user, since it owns the database and so is 00620 // the user that needs to run "CREATE LANGAUGE" 00621 $status = $this->getPgConnection( 'create-schema' ); 00622 if ( !$status->isOK() ) { 00623 return $status; 00624 } 00628 $conn = $status->value; 00629 00630 $exists = $conn->selectField( '"pg_catalog"."pg_language"', 1, 00631 array( 'lanname' => 'plpgsql' ), __METHOD__ ); 00632 if ( $exists ) { 00633 // Already exists, nothing to do 00634 return Status::newGood(); 00635 } 00636 00637 // plpgsql is not installed, but if we have a pg_pltemplate table, we 00638 // should be able to create it 00639 $exists = $conn->selectField( 00640 array( '"pg_catalog"."pg_class"', '"pg_catalog"."pg_namespace"' ), 00641 1, 00642 array( 00643 'pg_namespace.oid=relnamespace', 00644 'nspname' => 'pg_catalog', 00645 'relname' => 'pg_pltemplate', 00646 ), 00647 __METHOD__ ); 00648 if ( $exists ) { 00649 try { 00650 $conn->query( 'CREATE LANGUAGE plpgsql' ); 00651 } catch ( DBQueryError $e ) { 00652 return Status::newFatal( 'config-pg-no-plpgsql', $this->getVar( 'wgDBname' ) ); 00653 } 00654 } else { 00655 return Status::newFatal( 'config-pg-no-plpgsql', $this->getVar( 'wgDBname' ) ); 00656 } 00657 00658 return Status::newGood(); 00659 } 00660 }