From 34dc3650b61976555885a5c1075a788c44f4a63e Mon Sep 17 00:00:00 2001 From: Chad Horohoe Date: Tue, 1 Mar 2011 18:46:16 +0000 Subject: [PATCH] (bug 12070) After Installation MySQL was blocked. GRANT with IDENTIFIED BY will overwrite the password of an existing user silently, moving CREATE USER and GRANT to two separate operations so we can handle the different errors. Put in HISTORY, needs backporting with RELEASE-NOTES --- HISTORY | 1 + includes/installer/DatabaseInstaller.php | 1 + includes/installer/Installer.i18n.php | 6 +- includes/installer/MysqlInstaller.php | 111 +++++++++++++++++++++-- maintenance/users.sql | 12 --- 5 files changed, 111 insertions(+), 20 deletions(-) diff --git a/HISTORY b/HISTORY index 021185fa3e..e897e8860c 100644 --- a/HISTORY +++ b/HISTORY @@ -454,6 +454,7 @@ general notes. * (bug 25512) Subcategory list should not include category prefix for members. * (bug 20244) Installer does not validate SQLite database directory for stable path * (bug 1379) Installer directory conflicts with some hosts' configuration panel. +* (bug 12070) After Installation MySQL was blocked === API changes in 1.17 === * (bug 22738) Allow filtering by action type on query=logevent. diff --git a/includes/installer/DatabaseInstaller.php b/includes/installer/DatabaseInstaller.php index ec7a5efdd1..68c7b259f4 100644 --- a/includes/installer/DatabaseInstaller.php +++ b/includes/installer/DatabaseInstaller.php @@ -149,6 +149,7 @@ abstract class DatabaseInstaller { if( $this->db->tableExists( 'user' ) ) { $status->warning( 'config-install-tables-exist' ); + $this->enableLB(); return $status; } diff --git a/includes/installer/Installer.i18n.php b/includes/installer/Installer.i18n.php index c76fb3b29e..9c80bd1d4f 100644 --- a/includes/installer/Installer.i18n.php +++ b/includes/installer/Installer.i18n.php @@ -323,6 +323,8 @@ MyISAM databases tend to get corrupted more often than InnoDB databases.", This is more efficient than MySQL's UTF-8 mode, and allows you to use the full range of Unicode characters. In '''UTF-8 mode''', MySQL will know what character set your data is in, and can present and convert it appropriately, but it will not let you store characters above the [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].", + 'config-connection-user-exists' => 'The database user "$1" already exists. +Please choose a new username to create or do not try to create the user.', 'config-site-name' => 'Name of wiki:', 'config-site-name-help' => "This will appear in the title bar of the browser and in various other places.", 'config-site-name-blank' => 'Enter a site name.', @@ -475,7 +477,9 @@ Make sure that the user "$1" can write to the schema "$2".', 'config-pg-no-plpgsql' => 'You need to install the language PL/pgSQL in the database $1', 'config-pg-no-create-privs' => 'The account you specified for installation does not have enough privileges to create an account.', 'config-install-user' => 'Creating database user', - 'config-install-user-failed' => 'Granting permission to user "$1" failed: $2', + 'config-install-user-create-failed' => 'User "$1" already exists', + 'config-install-user-create-failed' => 'Creating user "$1" failed: $2', + 'config-install-user-grant-failed' => 'Granting permission to user "$1" failed: $2', 'config-install-tables' => 'Creating tables', 'config-install-tables-exist' => "'''Warning''': MediaWiki tables seem to already exist. Skipping creation.", diff --git a/includes/installer/MysqlInstaller.php b/includes/installer/MysqlInstaller.php index c5a01e32e7..84f20fc1a4 100644 --- a/includes/installer/MysqlInstaller.php +++ b/includes/installer/MysqlInstaller.php @@ -419,8 +419,6 @@ class MysqlInstaller extends DatabaseInstaller { } public function setupUser() { - global $IP; - if ( !$this->getVar( '_CreateDBAccount' ) ) { return Status::newGood(); } @@ -430,17 +428,116 @@ class MysqlInstaller extends DatabaseInstaller { return $status; } - $db = $this->getVar( 'wgDBname' ); - $this->db->selectDB( $db ); $this->setupSchemaVars(); - $error = $this->db->sourceFile( "$IP/maintenance/users.sql" ); - if ( $error !== true ) { - $status->fatal( 'config-install-user-failed', $this->getVar( 'wgDBuser' ), $error ); + $dbName = $this->getVar( 'wgDBname' ); + $this->db->selectDB( $dbName ); + $server = $this->getVar( 'wgDBserver' ); + $dbUser = $this->getVar( 'wgDBuser' ); + $password = $this->getVar( 'wgDBpassword' ); + $grantableNames = array(); + + // Before we blindly try to create a user that already has access, + try { // first attempt to connect to the database + new DatabaseMysql( + $server, + $dbUser, + $password, + false, + false, + 0, + $this->getVar( 'wgDBprefix' ) + ); + $grantableNames[] = $this->buildFullUserName( $dbUser, $server ); + $tryToCreate = false; + } catch ( DBConnectionError $e ) { + $tryToCreate = true; + } + + if( $tryToCreate ) { + $createHostList = array($server, + 'localhost', + 'localhost.localdomain', + '%' + ); + + $createHostList = array_unique( $createHostList ); + $escPass = $this->db->addQuotes( $password ); + + foreach( $createHostList as $host ) { + $fullName = $this->buildFullUserName( $dbUser, $host ); + if( !$this->userDefinitelyExists( $dbUser, $host ) ) { + try{ + $this->db->begin(); + $this->db->query( "CREATE USER $fullName IDENTIFIED BY $escPass", __METHOD__ ); + $this->db->commit(); + $grantableNames[] = $fullName; + } catch( DBQueryError $dqe ) { + if( $this->db->lastErrno() == 1396 /* ER_CANNOT_USER */ ) { + // User (probably) already exists + $this->db->rollback(); + $status->warning( 'config-install-user-alreadyexists', $dbUser ); + $grantableNames[] = $fullName; + break; + } else { + // If we couldn't create for some bizzare reason and the + // user probably doesn't exist, skip the grant + $this->db->rollback(); + $status->warning( 'config-install-user-create-failed', $dbUser, $dqe->getText() ); + } + } + } else { + $status->warning( 'config-install-user-alreadyexists', $dbUser ); + $grantableNames[] = $fullName; + break; + } + } + } + + // Try to grant to all the users we know exist or we were able to create + $escPass = $this->db->addQuotes( $password ); + $dbAllTables = $this->db->addIdentifierQuotes( $dbName ) . '.*'; + foreach( $grantableNames as $name ) { + try { + $this->db->begin(); + $this->db->query( "GRANT ALL PRIVILEGES ON $dbAllTables TO $name", __METHOD__ ); + $this->db->commit(); + } catch( DBQueryError $dqe ) { + $this->db->rollback(); + $status->fatal( 'config-install-user-grant-failed', $dbUser, $dqe->getText() ); + } } return $status; } + /** + * Return a formal 'User'@'Host' username for use in queries + * @param $name String Username, quotes will be added + * @param $host String Hostname, quotes will be added + * @return String + */ + private function buildFullUserName( $name, $host ) { + return $this->db->addQuotes( $name ) . '@' . $this->db->addQuotes( $host ); + } + + /** + * Try to see if the user account exists. Our "superuser" may not have + * access to mysql.user, so false means "no" or "maybe" + * @param $host String Hostname to check + * @param $user String Username to check + * @return boolean + */ + private function userDefinitelyExists( $host, $user ) { + try { + $res = $this->db->selectRow( 'mysql.user', array( 'Host', 'User' ), + array( 'Host' => $host, 'User' => $user ), __METHOD__ ); + return (bool)$res; + } catch( DBQueryError $dqe ) { + return false; + } + + } + /** * Return any table options to be applied to all tables that don't * override them. diff --git a/maintenance/users.sql b/maintenance/users.sql index 1db32ae265..e69de29bb2 100644 --- a/maintenance/users.sql +++ b/maintenance/users.sql @@ -1,12 +0,0 @@ --- SQL script to create required database users with proper --- access rights. This is run from the installation script --- which replaces the password variables with their values --- from local settings. --- - -GRANT ALL PRIVILEGES ON `{$wgDBname}`.* - TO '{$wgDBuser}'@'%' IDENTIFIED BY '{$wgDBpassword}'; -GRANT ALL PRIVILEGES ON `{$wgDBname}`.* - TO '{$wgDBuser}'@localhost IDENTIFIED BY '{$wgDBpassword}'; -GRANT ALL PRIVILEGES ON `{$wgDBname}`.* - TO '{$wgDBuser}'@localhost.localdomain IDENTIFIED BY '{$wgDBpassword}'; -- 2.20.1