diff --git a/application/forms/Setup/DatabaseCreationPage.php b/application/forms/Setup/DatabaseCreationPage.php new file mode 100644 index 0000000000..2c0d70cf84 --- /dev/null +++ b/application/forms/Setup/DatabaseCreationPage.php @@ -0,0 +1,144 @@ +setName('setup_database_creation'); + } + + /** + * Set the resource configuration to use + * + * @param array $config + * + * @return self + */ + public function setResourceConfig(array $config) + { + $this->config = $config; + return $this; + } + + /** + * Set the required database privileges + * + * @param array $privileges The required privileges + * + * @return self + */ + public function setDatabasePrivileges(array $privileges) + { + $this->databasePrivileges = $privileges; + return $this; + } + + /** + * @see Form::createElements() + */ + public function createElements(array $formData) + { + $this->addElement( + new Note( + 'description', + array( + 'value' => t( + 'It seems that either the database you defined earlier does not yet exist and cannot be created' + . ' using the provided access credentials or the database does not have the required schema to ' + . 'be operated by Icinga Web 2. Please provide appropriate access credentials to solve this.' + ) + ) + ) + ); + $this->addElement( + 'text', + 'username', + array( + 'required' => true, + 'label' => t('Username'), + 'description' => t('A user which is able to create databases and/or touch the database schema') + ) + ); + $this->addElement( + 'password', + 'password', + array( + 'required' => true, + 'label' => t('Password'), + 'description' => t('The password for the database user defined above') + ) + ); + } + + /** + * Validate the given form data and check whether the defined user has sufficient access rights + * + * @param array $data The data to validate + * + * @return bool + */ + public function isValid($data) + { + if (false === parent::isValid($data)) { + return false; + } + + $this->config['username'] = $this->getValue('username'); + $this->config['password'] = $this->getValue('password'); + $db = new DbTool($this->config); + + try { + $db->connectToDb(); + if (false === $db->checkPrivileges($this->databasePrivileges)) { + $this->addError( + t('The provided credentials do not have the required access rights to create the database schema.') + ); + return false; + } + } catch (PDOException $e) { + try { + $db->connectToHost(); + if (false === $db->checkPrivileges($this->databasePrivileges)) { + $this->addError( + t('The provided credentials cannot be used to create the database and/or the user.') + ); + return false; + } + } catch (PDOException $e) { + $this->addError($e->getMessage()); + return false; + } + } + + return true; + } +} diff --git a/library/Icinga/Application/WebSetup.php b/library/Icinga/Application/WebSetup.php index f16a441f22..bfdfe27cd8 100644 --- a/library/Icinga/Application/WebSetup.php +++ b/library/Icinga/Application/WebSetup.php @@ -4,6 +4,7 @@ namespace Icinga\Application; +use PDOException; use Icinga\Form\Setup\WelcomePage; use Icinga\Form\Setup\DbResourcePage; use Icinga\Form\Setup\PreferencesPage; @@ -13,9 +14,11 @@ use Icinga\Form\Setup\RequirementsPage; use Icinga\Form\Setup\GeneralConfigPage; use Icinga\Form\Setup\AuthenticationPage; +use Icinga\Form\Setup\DatabaseCreationPage; use Icinga\Web\Form; use Icinga\Web\Wizard; use Icinga\Web\Request; +use Icinga\Web\Setup\DbTool; use Icinga\Web\Setup\SetupWizard; use Icinga\Web\Setup\Requirements; use Icinga\Application\Platform; @@ -25,6 +28,31 @@ */ class WebSetup extends Wizard implements SetupWizard { + /** + * The database tables required by Icinga Web 2 + * + * @var array + */ + protected $databaseTables = array('account', 'preference'); + + /** + * The privileges required by Icinga Web 2 to setup the database + * + * @var array + */ + protected $databaseSetupPrivileges = array( + 'USAGE', + 'CREATE', + 'ALTER', + 'INSERT', + 'UPDATE', + 'DELETE', + 'TRUNCATE', + 'REFERENCES', + 'CREATE USER', + 'GRANT OPTION' + ); + /** * @see Wizard::init() */ @@ -37,8 +65,9 @@ protected function init() $this->addPage(new DbResourcePage()); $this->addPage(new LdapResourcePage()); $this->addPage(new AuthBackendPage()); - $this->addPage(new GeneralConfigPage()); $this->addPage(new AdminAccountPage()); + $this->addPage(new GeneralConfigPage()); + $this->addPage(new DatabaseCreationPage()); } /** @@ -68,6 +97,9 @@ public function setupPage(Form $page, Request $request) } elseif ($authData['type'] === 'ldap') { $page->setResourceConfig($this->getPageData('setup_ldap_resource')); } + } elseif ($page->getName() === 'setup_database_creation') { + $page->setDatabasePrivileges($this->databaseSetupPrivileges); + $page->setResourceConfig($this->getPageData('setup_db_resource')); } } @@ -85,6 +117,25 @@ protected function getNewPage($requestedPage, Form $originPage) } elseif ($newPage->getName() === 'setup_ldap_resource') { $authData = $this->getPageData('setup_authentication_type'); $skip = $authData['type'] !== 'ldap'; + } elseif ($newPage->getName() === 'setup_database_creation') { + if ($this->hasPageData('setup_db_resource')) { + $db = new DbTool($this->getPageData('setup_db_resource')); + + try { + $db->connectToDb(); + $diff = array_diff($this->databaseTables, $db->listTables()); + if (false === empty($diff)) { + $skip = $db->checkPrivileges($this->databaseSetupPrivileges); + } else { + $skip = true; + } + } catch (PDOException $e) { + $db->connectToHost(); + $skip = $db->checkPrivileges($this->databaseSetupPrivileges); + } + } else { + $skip = true; + } } if ($skip) { diff --git a/library/Icinga/Web/Setup/DbTool.php b/library/Icinga/Web/Setup/DbTool.php index 895065540d..a3ded97074 100644 --- a/library/Icinga/Web/Setup/DbTool.php +++ b/library/Icinga/Web/Setup/DbTool.php @@ -6,6 +6,9 @@ use PDO; use PDOException; +use LogicException; +use Zend_Db_Adapter_Pdo_Mysql; +use Zend_Db_Adapter_Pdo_Pgsql; use Icinga\Exception\ConfigurationError; /** @@ -14,11 +17,18 @@ class DbTool { /** - * The database connection + * The PDO database connection * * @var PDO */ - protected $conn; + protected $pdoConn; + + /** + * The Zend database adapter + * + * @var Zend_Db_Adapter_Pdo_Abstract + */ + protected $zendConn; /** * The resource configuration @@ -39,21 +49,27 @@ public function __construct(array $config) /** * Connect to the server + * + * @return self */ public function connectToHost() { $this->assertHostAccess(); $this->connect(); + return $this; } /** * Connect to the database + * + * @return self */ public function connectToDb() { $this->assertHostAccess(); $this->assertDatabaseAccess(); $this->connect($this->config['dbname']); + return $this; } /** @@ -88,6 +104,18 @@ protected function assertDatabaseAccess() } } + /** + * Assert that a connection with a database has been established + * + * @throws LogicException + */ + protected function assertConnectedToDb() + { + if ($this->zendConn === null) { + throw new LogicException('Not connected to database'); + } + } + /** * Establish a connection with the database or just the server by omitting the database name * @@ -95,11 +123,55 @@ protected function assertDatabaseAccess() */ public function connect($dbname = null) { - if ($this->conn !== null) { + $this->_pdoConnect($dbname); + if ($dbname !== null) { + $this->_zendConnect($dbname); + } + } + + /** + * Initialize Zend database adapter + * + * @param string $dbname The name of the database to connect with + * + * @throws ConfigurationError In case the resource type is not a supported PDO driver name + */ + protected function _zendConnect($dbname) + { + if ($this->zendConn !== null) { return; } - $this->conn = new PDO( + $config = array( + 'dbname' => $dbname, + 'username' => $this->config['username'], + 'password' => $this->config['password'] + ); + + if ($this->config['db'] === 'mysql') { + $this->zendConn = new Zend_Db_Adapter_Pdo_Mysql($config); + } elseif ($this->config['db'] === 'pgsql') { + $this->zendConn = new Zend_Db_Adapter_Pdo_Pgsql($config); + } else { + throw new ConfigurationError( + 'Failed to connect to database. Unsupported PDO driver "%s"', + $this->config['db'] + ); + } + } + + /** + * Initialize PDO connection + * + * @param string $dbname The name of the database to connect with + */ + protected function _pdoConnect($dbname) + { + if ($this->pdoConn !== null) { + return; + } + + $this->pdoConn = new PDO( $this->buildDsn($this->config['db'], $dbname), $this->config['username'], $this->config['password'], @@ -155,4 +227,27 @@ public function checkConnectivity() } } } + + /** + * Return whether the given privileges were granted + * + * @param array $privileges An array of strings with the required privilege names + * + * @return bool + */ + public function checkPrivileges(array $privileges) + { + return true; // TODO(7163): Implement privilege checks + } + + /** + * Return a list of all existing database tables + * + * @return array + */ + public function listTables() + { + $this->assertConnectedToDb(); + return $this->zendConn->listTables(); + } }