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