MediaWiki
REL1_23
|
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( 00087 'wgDBserver', 'wgDBport','wgDBname', 'wgDBmwschema', 00088 '_InstallUser', '_InstallPassword' 00089 ) ); 00090 00091 // Validate them 00092 $status = Status::newGood(); 00093 if ( !strlen( $newValues['wgDBname'] ) ) { 00094 $status->fatal( 'config-missing-db-name' ); 00095 } elseif ( !preg_match( '/^[a-zA-Z0-9_]+$/', $newValues['wgDBname'] ) ) { 00096 $status->fatal( 'config-invalid-db-name', $newValues['wgDBname'] ); 00097 } 00098 if ( !preg_match( '/^[a-zA-Z0-9_]*$/', $newValues['wgDBmwschema'] ) ) { 00099 $status->fatal( 'config-invalid-schema', $newValues['wgDBmwschema'] ); 00100 } 00101 if ( !strlen( $newValues['_InstallUser'] ) ) { 00102 $status->fatal( 'config-db-username-empty' ); 00103 } 00104 if ( !strlen( $newValues['_InstallPassword'] ) ) { 00105 $status->fatal( 'config-db-password-empty', $newValues['_InstallUser'] ); 00106 } 00107 00108 // Submit user box 00109 if ( $status->isOK() ) { 00110 $status->merge( $this->submitInstallUserBox() ); 00111 } 00112 if ( !$status->isOK() ) { 00113 return $status; 00114 } 00115 00116 $status = $this->getPgConnection( 'create-db' ); 00117 if ( !$status->isOK() ) { 00118 return $status; 00119 } 00123 $conn = $status->value; 00124 00125 // Check version 00126 $version = $conn->getServerVersion(); 00127 if ( version_compare( $version, $this->minimumVersion ) < 0 ) { 00128 return Status::newFatal( 'config-postgres-old', $this->minimumVersion, $version ); 00129 } 00130 00131 $this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) ); 00132 $this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) ); 00133 00134 return Status::newGood(); 00135 } 00136 00137 public function getConnection() { 00138 $status = $this->getPgConnection( 'create-tables' ); 00139 if ( $status->isOK() ) { 00140 $this->db = $status->value; 00141 } 00142 00143 return $status; 00144 } 00145 00146 public function openConnection() { 00147 return $this->openPgConnection( 'create-tables' ); 00148 } 00149 00157 protected function openConnectionWithParams( $user, $password, $dbName ) { 00158 $status = Status::newGood(); 00159 try { 00160 $db = new DatabasePostgres( 00161 $this->getVar( 'wgDBserver' ), 00162 $user, 00163 $password, 00164 $dbName 00165 ); 00166 $status->value = $db; 00167 } catch ( DBConnectionError $e ) { 00168 $status->fatal( 'config-connection-error', $e->getMessage() ); 00169 } 00170 00171 return $status; 00172 } 00173 00179 protected function getPgConnection( $type ) { 00180 if ( isset( $this->pgConns[$type] ) ) { 00181 return Status::newGood( $this->pgConns[$type] ); 00182 } 00183 $status = $this->openPgConnection( $type ); 00184 00185 if ( $status->isOK() ) { 00189 $conn = $status->value; 00190 $conn->clearFlag( DBO_TRX ); 00191 $conn->commit( __METHOD__ ); 00192 $this->pgConns[$type] = $conn; 00193 } 00194 00195 return $status; 00196 } 00197 00224 protected function openPgConnection( $type ) { 00225 switch ( $type ) { 00226 case 'create-db': 00227 return $this->openConnectionToAnyDB( 00228 $this->getVar( '_InstallUser' ), 00229 $this->getVar( '_InstallPassword' ) ); 00230 case 'create-schema': 00231 return $this->openConnectionWithParams( 00232 $this->getVar( '_InstallUser' ), 00233 $this->getVar( '_InstallPassword' ), 00234 $this->getVar( 'wgDBname' ) ); 00235 case 'create-tables': 00236 $status = $this->openPgConnection( 'create-schema' ); 00237 if ( $status->isOK() ) { 00241 $conn = $status->value; 00242 $safeRole = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) ); 00243 $conn->query( "SET ROLE $safeRole" ); 00244 } 00245 00246 return $status; 00247 default: 00248 throw new MWException( "Invalid special connection type: \"$type\"" ); 00249 } 00250 } 00251 00252 public function openConnectionToAnyDB( $user, $password ) { 00253 $dbs = array( 00254 'template1', 00255 'postgres', 00256 ); 00257 if ( !in_array( $this->getVar( 'wgDBname' ), $dbs ) ) { 00258 array_unshift( $dbs, $this->getVar( 'wgDBname' ) ); 00259 } 00260 $conn = false; 00261 $status = Status::newGood(); 00262 foreach ( $dbs as $db ) { 00263 try { 00264 $conn = new DatabasePostgres( 00265 $this->getVar( 'wgDBserver' ), 00266 $user, 00267 $password, 00268 $db ); 00269 } catch ( DBConnectionError $error ) { 00270 $conn = false; 00271 $status->fatal( 'config-pg-test-error', $db, 00272 $error->getMessage() ); 00273 } 00274 if ( $conn !== false ) { 00275 break; 00276 } 00277 } 00278 if ( $conn !== false ) { 00279 return Status::newGood( $conn ); 00280 } else { 00281 return $status; 00282 } 00283 } 00284 00285 protected function getInstallUserPermissions() { 00286 $status = $this->getPgConnection( 'create-db' ); 00287 if ( !$status->isOK() ) { 00288 return false; 00289 } 00293 $conn = $status->value; 00294 $superuser = $this->getVar( '_InstallUser' ); 00295 00296 $row = $conn->selectRow( '"pg_catalog"."pg_roles"', '*', 00297 array( 'rolname' => $superuser ), __METHOD__ ); 00298 00299 return $row; 00300 } 00301 00302 protected function canCreateAccounts() { 00303 $perms = $this->getInstallUserPermissions(); 00304 if ( !$perms ) { 00305 return false; 00306 } 00307 00308 return $perms->rolsuper === 't' || $perms->rolcreaterole === 't'; 00309 } 00310 00311 protected function isSuperUser() { 00312 $perms = $this->getInstallUserPermissions(); 00313 if ( !$perms ) { 00314 return false; 00315 } 00316 00317 return $perms->rolsuper === 't'; 00318 } 00319 00320 public function getSettingsForm() { 00321 if ( $this->canCreateAccounts() ) { 00322 $noCreateMsg = false; 00323 } else { 00324 $noCreateMsg = 'config-db-web-no-create-privs'; 00325 } 00326 $s = $this->getWebUserBox( $noCreateMsg ); 00327 00328 return $s; 00329 } 00330 00331 public function submitSettingsForm() { 00332 $status = $this->submitWebUserBox(); 00333 if ( !$status->isOK() ) { 00334 return $status; 00335 } 00336 00337 $same = $this->getVar( 'wgDBuser' ) === $this->getVar( '_InstallUser' ); 00338 00339 if ( $same ) { 00340 $exists = true; 00341 } else { 00342 // Check if the web user exists 00343 // Connect to the database with the install user 00344 $status = $this->getPgConnection( 'create-db' ); 00345 if ( !$status->isOK() ) { 00346 return $status; 00347 } 00348 $exists = $status->value->roleExists( $this->getVar( 'wgDBuser' ) ); 00349 } 00350 00351 // Validate the create checkbox 00352 if ( $this->canCreateAccounts() && !$same && !$exists ) { 00353 $create = $this->getVar( '_CreateDBAccount' ); 00354 } else { 00355 $this->setVar( '_CreateDBAccount', false ); 00356 $create = false; 00357 } 00358 00359 if ( !$create && !$exists ) { 00360 if ( $this->canCreateAccounts() ) { 00361 $msg = 'config-install-user-missing-create'; 00362 } else { 00363 $msg = 'config-install-user-missing'; 00364 } 00365 00366 return Status::newFatal( $msg, $this->getVar( 'wgDBuser' ) ); 00367 } 00368 00369 if ( !$exists ) { 00370 // No more checks to do 00371 return Status::newGood(); 00372 } 00373 00374 // Existing web account. Test the connection. 00375 $status = $this->openConnectionToAnyDB( 00376 $this->getVar( 'wgDBuser' ), 00377 $this->getVar( 'wgDBpassword' ) ); 00378 if ( !$status->isOK() ) { 00379 return $status; 00380 } 00381 00382 // The web user is conventionally the table owner in PostgreSQL 00383 // installations. Make sure the install user is able to create 00384 // objects on behalf of the web user. 00385 if ( $same || $this->canCreateObjectsForWebUser() ) { 00386 return Status::newGood(); 00387 } else { 00388 return Status::newFatal( 'config-pg-not-in-role' ); 00389 } 00390 } 00391 00397 protected function canCreateObjectsForWebUser() { 00398 if ( $this->isSuperUser() ) { 00399 return true; 00400 } 00401 00402 $status = $this->getPgConnection( 'create-db' ); 00403 if ( !$status->isOK() ) { 00404 return false; 00405 } 00406 $conn = $status->value; 00407 $installerId = $conn->selectField( '"pg_catalog"."pg_roles"', 'oid', 00408 array( 'rolname' => $this->getVar( '_InstallUser' ) ), __METHOD__ ); 00409 $webId = $conn->selectField( '"pg_catalog"."pg_roles"', 'oid', 00410 array( 'rolname' => $this->getVar( 'wgDBuser' ) ), __METHOD__ ); 00411 00412 return $this->isRoleMember( $conn, $installerId, $webId, $this->maxRoleSearchDepth ); 00413 } 00414 00423 protected function isRoleMember( $conn, $targetMember, $group, $maxDepth ) { 00424 if ( $targetMember === $group ) { 00425 // A role is always a member of itself 00426 return true; 00427 } 00428 // Get all members of the given group 00429 $res = $conn->select( '"pg_catalog"."pg_auth_members"', array( 'member' ), 00430 array( 'roleid' => $group ), __METHOD__ ); 00431 foreach ( $res as $row ) { 00432 if ( $row->member == $targetMember ) { 00433 // Found target member 00434 return true; 00435 } 00436 // Recursively search each member of the group to see if the target 00437 // is a member of it, up to the given maximum depth. 00438 if ( $maxDepth > 0 ) { 00439 if ( $this->isRoleMember( $conn, $targetMember, $row->member, $maxDepth - 1 ) ) { 00440 // Found member of member 00441 return true; 00442 } 00443 } 00444 } 00445 00446 return false; 00447 } 00448 00449 public function preInstall() { 00450 $createDbAccount = array( 00451 'name' => 'user', 00452 'callback' => array( $this, 'setupUser' ), 00453 ); 00454 $commitCB = array( 00455 'name' => 'pg-commit', 00456 'callback' => array( $this, 'commitChanges' ), 00457 ); 00458 $plpgCB = array( 00459 'name' => 'pg-plpgsql', 00460 'callback' => array( $this, 'setupPLpgSQL' ), 00461 ); 00462 $schemaCB = array( 00463 'name' => 'schema', 00464 'callback' => array( $this, 'setupSchema' ) 00465 ); 00466 00467 if ( $this->getVar( '_CreateDBAccount' ) ) { 00468 $this->parent->addInstallStep( $createDbAccount, 'database' ); 00469 } 00470 $this->parent->addInstallStep( $commitCB, 'interwiki' ); 00471 $this->parent->addInstallStep( $plpgCB, 'database' ); 00472 $this->parent->addInstallStep( $schemaCB, 'database' ); 00473 } 00474 00475 function setupDatabase() { 00476 $status = $this->getPgConnection( 'create-db' ); 00477 if ( !$status->isOK() ) { 00478 return $status; 00479 } 00480 $conn = $status->value; 00481 00482 $dbName = $this->getVar( 'wgDBname' ); 00483 00484 $exists = $conn->selectField( '"pg_catalog"."pg_database"', '1', 00485 array( 'datname' => $dbName ), __METHOD__ ); 00486 if ( !$exists ) { 00487 $safedb = $conn->addIdentifierQuotes( $dbName ); 00488 $conn->query( "CREATE DATABASE $safedb", __METHOD__ ); 00489 } 00490 00491 return Status::newGood(); 00492 } 00493 00494 function setupSchema() { 00495 // Get a connection to the target database 00496 $status = $this->getPgConnection( 'create-schema' ); 00497 if ( !$status->isOK() ) { 00498 return $status; 00499 } 00500 $conn = $status->value; 00501 00502 // Create the schema if necessary 00503 $schema = $this->getVar( 'wgDBmwschema' ); 00504 $safeschema = $conn->addIdentifierQuotes( $schema ); 00505 $safeuser = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) ); 00506 if ( !$conn->schemaExists( $schema ) ) { 00507 try { 00508 $conn->query( "CREATE SCHEMA $safeschema AUTHORIZATION $safeuser" ); 00509 } catch ( DBQueryError $e ) { 00510 return Status::newFatal( 'config-install-pg-schema-failed', 00511 $this->getVar( '_InstallUser' ), $schema ); 00512 } 00513 } 00514 00515 // Select the new schema in the current connection 00516 $conn->determineCoreSchema( $schema ); 00517 00518 return Status::newGood(); 00519 } 00520 00521 function commitChanges() { 00522 $this->db->commit( __METHOD__ ); 00523 00524 return Status::newGood(); 00525 } 00526 00527 function setupUser() { 00528 if ( !$this->getVar( '_CreateDBAccount' ) ) { 00529 return Status::newGood(); 00530 } 00531 00532 $status = $this->getPgConnection( 'create-db' ); 00533 if ( !$status->isOK() ) { 00534 return $status; 00535 } 00536 $conn = $status->value; 00537 00538 $safeuser = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) ); 00539 $safepass = $conn->addQuotes( $this->getVar( 'wgDBpassword' ) ); 00540 00541 // Check if the user already exists 00542 $userExists = $conn->roleExists( $this->getVar( 'wgDBuser' ) ); 00543 if ( !$userExists ) { 00544 // Create the user 00545 try { 00546 $sql = "CREATE ROLE $safeuser NOCREATEDB LOGIN PASSWORD $safepass"; 00547 00548 // If the install user is not a superuser, we need to make the install 00549 // user a member of the new user's group, so that the install user will 00550 // be able to create a schema and other objects on behalf of the new user. 00551 if ( !$this->isSuperUser() ) { 00552 $sql .= ' ROLE' . $conn->addIdentifierQuotes( $this->getVar( '_InstallUser' ) ); 00553 } 00554 00555 $conn->query( $sql, __METHOD__ ); 00556 } catch ( DBQueryError $e ) { 00557 return Status::newFatal( 'config-install-user-create-failed', 00558 $this->getVar( 'wgDBuser' ), $e->getMessage() ); 00559 } 00560 } 00561 00562 return Status::newGood(); 00563 } 00564 00565 function getLocalSettings() { 00566 $port = $this->getVar( 'wgDBport' ); 00567 $schema = $this->getVar( 'wgDBmwschema' ); 00568 00569 return "# Postgres specific settings 00570 \$wgDBport = \"{$port}\"; 00571 \$wgDBmwschema = \"{$schema}\";"; 00572 } 00573 00574 public function preUpgrade() { 00575 global $wgDBuser, $wgDBpassword; 00576 00577 # Normal user and password are selected after this step, so for now 00578 # just copy these two 00579 $wgDBuser = $this->getVar( '_InstallUser' ); 00580 $wgDBpassword = $this->getVar( '_InstallPassword' ); 00581 } 00582 00583 public function createTables() { 00584 $schema = $this->getVar( 'wgDBmwschema' ); 00585 00586 $status = $this->getConnection(); 00587 if ( !$status->isOK() ) { 00588 return $status; 00589 } 00590 00594 $conn = $status->value; 00595 00596 if ( $conn->tableExists( 'archive' ) ) { 00597 $status->warning( 'config-install-tables-exist' ); 00598 $this->enableLB(); 00599 00600 return $status; 00601 } 00602 00603 $conn->begin( __METHOD__ ); 00604 00605 if ( !$conn->schemaExists( $schema ) ) { 00606 $status->fatal( 'config-install-pg-schema-not-exist' ); 00607 00608 return $status; 00609 } 00610 $error = $conn->sourceFile( $conn->getSchemaPath() ); 00611 if ( $error !== true ) { 00612 $conn->reportQueryError( $error, 0, '', __METHOD__ ); 00613 $conn->rollback( __METHOD__ ); 00614 $status->fatal( 'config-install-tables-failed', $error ); 00615 } else { 00616 $conn->commit( __METHOD__ ); 00617 } 00618 // Resume normal operations 00619 if ( $status->isOk() ) { 00620 $this->enableLB(); 00621 } 00622 00623 return $status; 00624 } 00625 00626 public function setupPLpgSQL() { 00627 // Connect as the install user, since it owns the database and so is 00628 // the user that needs to run "CREATE LANGAUGE" 00629 $status = $this->getPgConnection( 'create-schema' ); 00630 if ( !$status->isOK() ) { 00631 return $status; 00632 } 00636 $conn = $status->value; 00637 00638 $exists = $conn->selectField( '"pg_catalog"."pg_language"', 1, 00639 array( 'lanname' => 'plpgsql' ), __METHOD__ ); 00640 if ( $exists ) { 00641 // Already exists, nothing to do 00642 return Status::newGood(); 00643 } 00644 00645 // plpgsql is not installed, but if we have a pg_pltemplate table, we 00646 // should be able to create it 00647 $exists = $conn->selectField( 00648 array( '"pg_catalog"."pg_class"', '"pg_catalog"."pg_namespace"' ), 00649 1, 00650 array( 00651 'pg_namespace.oid=relnamespace', 00652 'nspname' => 'pg_catalog', 00653 'relname' => 'pg_pltemplate', 00654 ), 00655 __METHOD__ ); 00656 if ( $exists ) { 00657 try { 00658 $conn->query( 'CREATE LANGUAGE plpgsql' ); 00659 } catch ( DBQueryError $e ) { 00660 return Status::newFatal( 'config-pg-no-plpgsql', $this->getVar( 'wgDBname' ) ); 00661 } 00662 } else { 00663 return Status::newFatal( 'config-pg-no-plpgsql', $this->getVar( 'wgDBname' ) ); 00664 } 00665 00666 return Status::newGood(); 00667 } 00668 }