MediaWiki  REL1_23
PostgresInstaller.php
Go to the documentation of this file.
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 }