diff --git a/library/Icinga/Application/WebInstaller.php b/library/Icinga/Application/WebInstaller.php index 736e74cd9..e09b58d55 100644 --- a/library/Icinga/Application/WebInstaller.php +++ b/library/Icinga/Application/WebInstaller.php @@ -8,6 +8,7 @@ use Exception; use Zend_Config; use PDOException; use Icinga\Web\Setup\DbTool; +use Icinga\Application\Icinga; use Icinga\Application\Config; use Icinga\Web\Setup\Installer; use Icinga\Config\PreservingIniWriter; @@ -49,6 +50,17 @@ class WebInstaller implements Installer { $success = true; + if (isset($this->pageData['setup_db_resource']) && ! $this->pageData['setup_db_resource']['skip_validation']) { + try { + $this->setupDatabase(); + } catch (Exception $e) { + $this->log(sprintf(t('Failed to set up the database: %s'), $e->getMessage()), false); + return false; // Bail out as early as possible as not being able to setup the database is fatal + } + + $this->log(t('The database has been successfully set up!')); + } + $configIniPath = Config::resolvePath('config.ini'); try { $this->writeConfigIni($configIniPath); @@ -189,6 +201,159 @@ class WebInstaller implements Installer $writer->write(); } + /** + * Setup the database + */ + protected function setupDatabase() + { + $resourceConfig = $this->pageData['setup_db_resource']; + if (isset($this->pageData['setup_database_creation'])) { + $resourceConfig['username'] = $this->pageData['setup_database_creation']['username']; + $resourceConfig['password'] = $this->pageData['setup_database_creation']['password']; + } + + $db = new DbTool($resourceConfig); + if ($resourceConfig['db'] === 'mysql') { + $this->setupMysqlDatabase($db); + } elseif ($resourceConfig['db'] === 'pgsql') { + $this->setupPgsqlDatabase($db); + } + } + + /** + * Setup a MySQL database + * + * @param DbTool $db The database connection wrapper to use + * + * @todo Escape user input or make use of prepared statements! + */ + private function setupMysqlDatabase(DbTool $db) + { + try { + $db->connectToDb(); + $this->log(sprintf( + t('Successfully connected to existing database "%s"...'), + $this->pageData['setup_db_resource']['dbname'] + )); + } catch (PDOException $e) { + $db->connectToHost(); + $this->log(sprintf( + t('Creating new database "%s"...'), + $this->pageData['setup_db_resource']['dbname'] + )); + $db->exec('CREATE DATABASE ' . $this->pageData['setup_db_resource']['dbname']); + $db->reconnect($this->pageData['setup_db_resource']['dbname']); + } + + $loginIdent = "'" . $this->pageData['setup_db_resource']['username'] . "'@'" . Platform::getFqdn() . "'"; + if (false === array_search($loginIdent, $db->listLogins())) { + $this->log(sprintf( + t('Creating login "%s"...'), + $this->pageData['setup_db_resource']['username'] + )); + $db->exec( + "CREATE USER $loginIdent IDENTIFIED BY '" . + $this->pageData['setup_db_resource']['password'] . "'" + ); + } else { + $this->log(sprintf( + t('Login "%s" already exists...'), + $this->pageData['setup_db_resource']['username'] + )); + } + + $diff = array_diff(array('account', 'preference'), $db->listTables()); + if (empty($diff)) { + $this->log(t('Database schema already exists...')); + } else { + $this->log(t('Creating database schema...')); + $db->import(Icinga::app()->getApplicationDir() . '/../etc/schema/mysql.sql'); + } + + $privileges = array('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'EXECUTE', 'CREATE TEMPORARY TABLES'); + if ($db->checkPrivileges(array_merge($privileges, array('GRANT OPTION')))) { + $this->log(sprintf( + t('Granting required privileges to login "%s"...'), + $this->pageData['setup_db_resource']['username'] + )); + $db->exec(sprintf( + "GRANT %s ON %s.* TO %s", + join(',', $privileges), + $this->pageData['setup_db_resource']['dbname'], + $loginIdent + )); + } + } + + /** + * Setup a PostgreSQL database + * + * @param DbTool $db The database connection wrapper to use + * + * @todo Escape user input or make use of prepared statements! + */ + private function setupPgsqlDatabase(DbTool $db) + { + try { + $db->connectToDb(); + $this->log(sprintf( + t('Successfully connected to existing database "%s"...'), + $this->pageData['setup_db_resource']['dbname'] + )); + } catch (PDOException $e) { + $db->connectToHost(); + $this->log(sprintf( + t('Creating new database "%s"...'), + $this->pageData['setup_db_resource']['dbname'] + )); + $db->exec('CREATE DATABASE ' . $this->pageData['setup_db_resource']['dbname']); + $db->reconnect($this->pageData['setup_db_resource']['dbname']); + } + + if (false === array_search($this->pageData['setup_db_resource']['username'], $db->listLogins())) { + $this->log(sprintf( + t('Creating login "%s"...'), + $this->pageData['setup_db_resource']['username'] + )); + $db->exec(sprintf( + "CREATE USER %s WITH PASSWORD '%s'", + $this->pageData['setup_db_resource']['username'], + $this->pageData['setup_db_resource']['password'] + )); + } else { + $this->log(sprintf( + t('Login "%s" already exists...'), + $this->pageData['setup_db_resource']['username'] + )); + } + + $diff = array_diff(array('account', 'preference'), $db->listTables()); + if (empty($diff)) { + $this->log(t('Database schema already exists...')); + } else { + $this->log(t('Creating database schema...')); + $db->import(Icinga::app()->getApplicationDir() . '/../etc/schema/pgsql.sql'); + } + + $privileges = array('SELECT', 'INSERT', 'UPDATE', 'DELETE'); + if ($db->checkPrivileges(array_merge($privileges, array('GRANT OPTION')))) { + $this->log(sprintf( + t('Granting required privileges to login "%s"...'), + $this->pageData['setup_db_resource']['username'] + )); + $db->exec(sprintf( + "GRANT %s ON TABLE account TO %s", + join(',', $privileges), + $this->pageData['setup_db_resource']['username'] + )); + $db->exec(sprintf( + "GRANT %s ON TABLE preference TO %s", + join(',', $privileges), + $this->pageData['setup_db_resource']['username'] + )); + } + } + /** * @see Installer::getSummary() */ diff --git a/library/Icinga/Web/Setup/DbTool.php b/library/Icinga/Web/Setup/DbTool.php index a3ded9707..982c6bc67 100644 --- a/library/Icinga/Web/Setup/DbTool.php +++ b/library/Icinga/Web/Setup/DbTool.php @@ -9,6 +9,7 @@ use PDOException; use LogicException; use Zend_Db_Adapter_Pdo_Mysql; use Zend_Db_Adapter_Pdo_Pgsql; +use Icinga\Util\File; use Icinga\Exception\ConfigurationError; /** @@ -129,6 +130,18 @@ class DbTool } } + /** + * Reestablish a connection with the database or just the server by omitting the database name + * + * @param string $dbname The name of the database to connect to + */ + public function reconnect($dbname = null) + { + $this->pdoConn = null; + $this->zendConn = null; + $this->connect($dbname); + } + /** * Initialize Zend database adapter * @@ -228,6 +241,35 @@ class DbTool } } + /** + * Execute a SQL statement and return the affected row count + * + * @param string $statement The statement to execute + * + * @return int + */ + public function exec($statement) + { + return $this->pdoConn->exec($statement); + } + + /** + * Import the given SQL file + * + * @param string $filepath The file to import + */ + public function import($filepath) + { + $file = new File($filepath); + $content = join(PHP_EOL, iterator_to_array($file)); // There is no fread() before PHP 5.5 :( + + foreach (explode(';', $content) as $statement) { + if (($statement = trim($statement)) !== '') { + $this->exec($statement); + } + } + } + /** * Return whether the given privileges were granted * @@ -250,4 +292,28 @@ class DbTool $this->assertConnectedToDb(); return $this->zendConn->listTables(); } + + /** + * Return a list of all available database logins + * + * @return array + */ + public function listLogins() + { + $users = array(); + + if ($this->config['db'] === 'mysql') { + $query = $this->pdoConn->query('SELECT DISTINCT grantee FROM information_schema.user_privileges'); + foreach ($query->fetchAll() as $row) { + $users[] = $row['grantee']; + } + } elseif ($this->config['db'] === 'pgsql') { + $query = $this->pdoConn->query('SELECT usename FROM pg_catalog.pg_user'); + foreach ($query->fetchAll() as $row) { + $users[] = $row['usename']; + } + } + + return $users; + } }