parent
0c8aa6502b
commit
581935c26f
|
@ -22,11 +22,18 @@ class DatabaseCreationPage extends Form
|
|||
protected $config;
|
||||
|
||||
/**
|
||||
* The required database privileges
|
||||
* The required privileges to setup the database
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $databasePrivileges;
|
||||
protected $databaseSetupPrivileges;
|
||||
|
||||
/**
|
||||
* The required privileges to operate the database
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $databaseUsagePrivileges;
|
||||
|
||||
/**
|
||||
* Initialize this page
|
||||
|
@ -50,15 +57,28 @@ class DatabaseCreationPage extends Form
|
|||
}
|
||||
|
||||
/**
|
||||
* Set the required database privileges
|
||||
* Set the required privileges to setup the database
|
||||
*
|
||||
* @param array $privileges The required privileges
|
||||
* @param array $privileges The privileges
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setDatabasePrivileges(array $privileges)
|
||||
public function setDatabaseSetupPrivileges(array $privileges)
|
||||
{
|
||||
$this->databasePrivileges = $privileges;
|
||||
$this->databaseSetupPrivileges = $privileges;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the required privileges to operate the database
|
||||
*
|
||||
* @param array $privileges The privileges
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setDatabaseUsagePrivileges(array $privileges)
|
||||
{
|
||||
$this->databaseUsagePrivileges = $privileges;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -130,57 +150,42 @@ class DatabaseCreationPage extends Form
|
|||
return true;
|
||||
}
|
||||
|
||||
$this->config['username'] = $this->getValue('username');
|
||||
$this->config['password'] = $this->getValue('password');
|
||||
$db = new DbTool($this->config);
|
||||
$database = $this->config['dbname'];
|
||||
$dbtype = $this->config['db'];
|
||||
$config = $this->config;
|
||||
$config['username'] = $this->getValue('username');
|
||||
$config['password'] = $this->getValue('password');
|
||||
$db = new DbTool($config);
|
||||
|
||||
try {
|
||||
$error = false;
|
||||
$msg = '';
|
||||
if ($dbtype === 'pgsql') {
|
||||
$db->connectToHost();
|
||||
if (
|
||||
false === $db->checkPgsqlGrantOption($this->databasePrivileges, $database, 'account') &&
|
||||
false === $db->checkPgsqlGrantOption($this->databasePrivileges, $database, 'preference')
|
||||
) {
|
||||
$error = true;
|
||||
$msg = sprintf(t('The role does not seem to have permission to create ' .
|
||||
' or to grant access to the database "%s" and the tables "account", "preference".'), $database);
|
||||
}
|
||||
} else if ($dbtype === 'mysql') {
|
||||
$db->connectToHost();
|
||||
if (false === $db->checkMysqlGrantOption($this->databasePrivileges)) {
|
||||
$error = true;
|
||||
$msg = sprintf(t('The user does not seem to have permission to create ' .
|
||||
' or to grant access to the database %s.'), $database);
|
||||
}
|
||||
}
|
||||
if ($error) {
|
||||
$this->addSkipValidationCheckbox();
|
||||
$this->addError(t(
|
||||
'The provided credentials do not have the required access rights to create the database schema: '
|
||||
) . $msg);
|
||||
return false;
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$db->connectToDb(); // Are we able to login on the database?
|
||||
} catch (PDOException $_) {
|
||||
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.')
|
||||
);
|
||||
$this->addSkipValidationCheckbox();
|
||||
return false;
|
||||
}
|
||||
$db->connectToHost(); // Are we able to login on the server?
|
||||
} catch (PDOException $e) {
|
||||
// We are NOT able to login on the server..
|
||||
$this->addError($e->getMessage());
|
||||
$this->addSkipValidationCheckbox();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// In case we are connected the credentials filled into this
|
||||
// form need to be granted to create databases, users...
|
||||
if (false === $db->checkPrivileges($this->databaseSetupPrivileges)) {
|
||||
$this->addError(t('The provided credentials cannot be used to create the database and/or the user.'));
|
||||
$this->addSkipValidationCheckbox();
|
||||
return false;
|
||||
}
|
||||
|
||||
// ...and to grant all required usage privileges to others
|
||||
if (false === $db->isGrantable($this->databaseUsagePrivileges)) {
|
||||
$this->addError(sprintf(
|
||||
t('The provided credentials cannot be used to grant all required privileges to the login "%s".'),
|
||||
$this->config['username']
|
||||
));
|
||||
$this->addSkipValidationCheckbox();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -44,14 +44,37 @@ class WebSetup extends Wizard implements SetupWizard
|
|||
*/
|
||||
protected $databaseSetupPrivileges = array(
|
||||
'CREATE',
|
||||
'ALTER',
|
||||
'REFERENCES',
|
||||
'CREATE USER', // MySQL
|
||||
'CREATEROLE' // PostgreSQL
|
||||
);
|
||||
|
||||
/**
|
||||
* The privileges required by Icinga Web 2 to operate the database
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $databaseUsagePrivileges = array(
|
||||
'SELECT',
|
||||
'INSERT',
|
||||
'UPDATE',
|
||||
'DELETE',
|
||||
'REFERENCES',
|
||||
'EXECUTE',
|
||||
'CREATE TEMPORARY TABLES',
|
||||
'CREATE USER'
|
||||
'TEMPORARY', // PostgreSql
|
||||
'CREATE TEMPORARY TABLES' // MySQL
|
||||
);
|
||||
|
||||
/**
|
||||
* The database tables operated by Icinga Web 2
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $databaseTables = array(
|
||||
'icingaweb_group',
|
||||
'icingaweb_group_membership',
|
||||
'icingaweb_user',
|
||||
'icingaweb_user_preference'
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -110,7 +133,8 @@ class WebSetup extends Wizard implements SetupWizard
|
|||
$page->setResourceConfig($this->getPageData('setup_ldap_resource'));
|
||||
}
|
||||
} elseif ($page->getName() === 'setup_database_creation') {
|
||||
$page->setDatabasePrivileges($this->databaseSetupPrivileges);
|
||||
$page->setDatabaseSetupPrivileges($this->databaseSetupPrivileges);
|
||||
$page->setDatabaseUsagePrivileges($this->databaseUsagePrivileges);
|
||||
$page->setResourceConfig($this->getPageData('setup_db_resource'));
|
||||
} elseif ($page->getName() === 'setup_summary') {
|
||||
$page->setSubjectTitle('Icinga Web 2');
|
||||
|
@ -171,18 +195,24 @@ class WebSetup extends Wizard implements SetupWizard
|
|||
$db = new DbTool($config);
|
||||
|
||||
try {
|
||||
$db->connectToDb();
|
||||
if (array_search('account', $db->listTables()) === false) {
|
||||
$skip = $db->checkPrivileges($this->databaseSetupPrivileges);
|
||||
$db->connectToDb(); // Are we able to login on the database?
|
||||
if (array_search(key($this->databaseTables), $db->listTables()) === false) {
|
||||
// In case the database schema does not yet exist the user
|
||||
// needs the privileges to create and setup the database
|
||||
$skip = $db->checkPrivileges($this->databaseSetupPrivileges, $this->databaseTables);
|
||||
} else {
|
||||
$skip = true;
|
||||
// In case the database schema exists the user needs the required privileges
|
||||
// to operate the database, if those are missing we ask for another user
|
||||
$skip = $db->checkPrivileges($this->databaseUsagePrivileges, $this->databaseTables);
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
} catch (PDOException $_) {
|
||||
try {
|
||||
$db->connectToHost();
|
||||
$skip = $db->checkPrivileges($this->databaseSetupPrivileges);
|
||||
} catch (PDOException $e) {
|
||||
// skip should already be false, nothing to do
|
||||
$db->connectToHost(); // Are we able to login on the server?
|
||||
// It is not possible to reliably determine whether a database exists or not if a user can't
|
||||
// log in to the database, so we just require the user to be able to create the database
|
||||
$skip = $db->checkPrivileges($this->databaseSetupPrivileges, $this->databaseTables);
|
||||
} catch (PDOException $_) {
|
||||
// We are NOT able to login on the server..
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -255,6 +285,8 @@ class WebSetup extends Wizard implements SetupWizard
|
|||
) {
|
||||
$installer->addStep(
|
||||
new DatabaseStep(array(
|
||||
'tables' => $this->databaseTables,
|
||||
'privileges' => $this->databaseUsagePrivileges,
|
||||
'resourceConfig' => $pageData['setup_db_resource'],
|
||||
'adminName' => isset($pageData['setup_database_creation']['username'])
|
||||
? $pageData['setup_database_creation']['username']
|
||||
|
|
|
@ -9,7 +9,6 @@ use PDOException;
|
|||
use Icinga\Web\Setup\Step;
|
||||
use Icinga\Web\Setup\DbTool;
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Application\Platform;
|
||||
use Icinga\Exception\InstallException;
|
||||
|
||||
class DatabaseStep extends Step
|
||||
|
@ -61,44 +60,40 @@ class DatabaseStep extends Step
|
|||
t('Successfully connected to existing database "%s"...'),
|
||||
$this->data['resourceConfig']['dbname']
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
} catch (PDOException $_) {
|
||||
$db->connectToHost();
|
||||
$this->log(t('Creating new database "%s"...'), $this->data['resourceConfig']['dbname']);
|
||||
$db->exec('CREATE DATABASE ' . $db->quoteIdentifier($this->data['resourceConfig']['dbname']));
|
||||
$db->reconnect($this->data['resourceConfig']['dbname']);
|
||||
}
|
||||
|
||||
if ($db->hasLogin(
|
||||
$this->data['resourceConfig']['username'],
|
||||
$this->data['resourceConfig']['password']
|
||||
)) {
|
||||
if (array_search(key($this->data['tables']), $db->listTables()) !== false) {
|
||||
$this->log(t('Database schema already exists...'));
|
||||
} else {
|
||||
$this->log(t('Creating database schema...'));
|
||||
$db->import(Icinga::app()->getApplicationDir() . '/../etc/schema/mysql.schema.sql');
|
||||
}
|
||||
|
||||
if ($db->hasLogin($this->data['resourceConfig']['username'])) {
|
||||
$this->log(t('Login "%s" already exists...'), $this->data['resourceConfig']['username']);
|
||||
} else {
|
||||
$this->log(t('Creating login "%s"...'), $this->data['resourceConfig']['username']);
|
||||
$db->addLogin($this->data['resourceConfig']['username'], $this->data['resourceConfig']['password']);
|
||||
}
|
||||
|
||||
if (array_search('account', $db->listTables()) !== false) {
|
||||
$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->checkMysqlGrantOption(array_merge($privileges))) {
|
||||
$this->log(t('Granting required privileges to login "%s"...'), $this->data['resourceConfig']['username']);
|
||||
$db->exec(sprintf(
|
||||
"GRANT %s ON %s.* TO %s@'%%'",
|
||||
join(',', $privileges),
|
||||
$db->quoteIdentifier($this->data['resourceConfig']['dbname']),
|
||||
$db->quoteIdentifier($this->data['resourceConfig']['username'])
|
||||
));
|
||||
} else {
|
||||
$username = $this->data['resourceConfig']['username'];
|
||||
if ($db->checkPrivileges($this->data['privileges'], $this->data['tables'], $username)) {
|
||||
$this->log(
|
||||
t('Required privileges were already granted to login "%s".'),
|
||||
$this->data['resourceConfig']['username']
|
||||
);
|
||||
} else {
|
||||
$this->log(t('Granting required privileges to login "%s"...'), $this->data['resourceConfig']['username']);
|
||||
$db->grantPrivileges(
|
||||
$this->data['privileges'],
|
||||
$this->data['tables'],
|
||||
$this->data['resourceConfig']['username']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,52 +105,43 @@ class DatabaseStep extends Step
|
|||
t('Successfully connected to existing database "%s"...'),
|
||||
$this->data['resourceConfig']['dbname']
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
} catch (PDOException $_) {
|
||||
$db->connectToHost();
|
||||
$this->log(t('Creating new database "%s"...'), $this->data['resourceConfig']['dbname']);
|
||||
$db->exec('CREATE DATABASE ' . $db->quoteIdentifier($this->data['resourceConfig']['dbname']));
|
||||
$db->exec(sprintf(
|
||||
"CREATE DATABASE %s WITH ENCODING 'UTF-8'",
|
||||
$db->quoteIdentifier($this->data['resourceConfig']['dbname'])
|
||||
));
|
||||
$db->reconnect($this->data['resourceConfig']['dbname']);
|
||||
}
|
||||
|
||||
if ($db->hasLogin(
|
||||
$this->data['resourceConfig']['username'],
|
||||
$this->data['resourceConfig']['password']
|
||||
)) {
|
||||
if (array_search(key($this->data['tables']), $db->listTables()) !== false) {
|
||||
$this->log(t('Database schema already exists...'));
|
||||
} else {
|
||||
$this->log(t('Creating database schema...'));
|
||||
$db->import(Icinga::app()->getApplicationDir() . '/../etc/schema/pgsql.schema.sql');
|
||||
}
|
||||
|
||||
if ($db->hasLogin($this->data['resourceConfig']['username'])) {
|
||||
$this->log(t('Login "%s" already exists...'), $this->data['resourceConfig']['username']);
|
||||
} else {
|
||||
$this->log(t('Creating login "%s"...'), $this->data['resourceConfig']['username']);
|
||||
$db->addLogin($this->data['resourceConfig']['username'], $this->data['resourceConfig']['password']);
|
||||
}
|
||||
|
||||
if (array_search('account', $db->listTables()) !== false) {
|
||||
$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', 'REFERENCES');
|
||||
if ($db->checkPgsqlGrantOption(
|
||||
$privileges,
|
||||
$this->data['resourceConfig']['dbname'],
|
||||
'account'
|
||||
)) {
|
||||
$this->log(t('Granting required privileges to login "%s"...'), $this->data['resourceConfig']['username']);
|
||||
$db->exec(sprintf(
|
||||
"GRANT %s ON TABLE account TO %s",
|
||||
join(',', $privileges),
|
||||
$db->quoteIdentifier($this->data['resourceConfig']['username'])
|
||||
));
|
||||
$db->exec(sprintf(
|
||||
"GRANT %s ON TABLE preference TO %s",
|
||||
join(',', $privileges),
|
||||
$db->quoteIdentifier($this->data['resourceConfig']['username'])
|
||||
));
|
||||
} else {
|
||||
$username = $this->data['resourceConfig']['username'];
|
||||
if ($db->checkPrivileges($this->data['privileges'], $this->data['tables'], $username)) {
|
||||
$this->log(
|
||||
t('Required privileges were already granted to login "%s".'),
|
||||
$this->data['resourceConfig']['username']
|
||||
);
|
||||
} else {
|
||||
$this->log(t('Granting required privileges to login "%s"...'), $this->data['resourceConfig']['username']);
|
||||
$db->grantPrivileges(
|
||||
$this->data['privileges'],
|
||||
$this->data['tables'],
|
||||
$this->data['resourceConfig']['username']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,48 +159,71 @@ class DatabaseStep extends Step
|
|||
|
||||
try {
|
||||
$db->connectToDb();
|
||||
if (array_search('account', $db->listTables()) === false) {
|
||||
$message = sprintf(
|
||||
t(
|
||||
'The database user "%s" will be used to setup the missing'
|
||||
. ' schema required by Icinga Web 2 in database "%s".'
|
||||
),
|
||||
$resourceConfig['username'],
|
||||
$resourceConfig['dbname']
|
||||
);
|
||||
if (array_search(key($this->data['tables']), $db->listTables()) === false) {
|
||||
if ($resourceConfig['username'] !== $this->data['resourceConfig']['username']) {
|
||||
$message = sprintf(
|
||||
t(
|
||||
'The database user "%s" will be used to setup the missing schema required by Icinga'
|
||||
. ' Web 2 in database "%s" and to grant access to it to a new login called "%s".'
|
||||
),
|
||||
$resourceConfig['username'],
|
||||
$resourceConfig['dbname'],
|
||||
$this->data['resourceConfig']['username']
|
||||
);
|
||||
} else {
|
||||
$message = sprintf(
|
||||
t(
|
||||
'The database user "%s" will be used to setup the missing'
|
||||
. ' schema required by Icinga Web 2 in database "%s".'
|
||||
),
|
||||
$resourceConfig['username'],
|
||||
$resourceConfig['dbname']
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$message = sprintf(
|
||||
t('The database "%s" already seems to be fully set up. No action required.'),
|
||||
$resourceConfig['dbname']
|
||||
);
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
} catch (PDOException $_) {
|
||||
try {
|
||||
$db->connectToHost();
|
||||
if ($db->hasLogin(
|
||||
$this->data['resourceConfig']['username'],
|
||||
$this->data['resourceConfig']['password']
|
||||
)) {
|
||||
if ($resourceConfig['username'] !== $this->data['resourceConfig']['username']) {
|
||||
if ($db->hasLogin($this->data['resourceConfig']['username'])) {
|
||||
$message = sprintf(
|
||||
t(
|
||||
'The database user "%s" will be used to create the missing database'
|
||||
. ' "%s" with the schema required by Icinga Web 2 and to grant'
|
||||
. ' access to it to an existing login called "%s".'
|
||||
),
|
||||
$resourceConfig['username'],
|
||||
$resourceConfig['dbname'],
|
||||
$this->data['resourceConfig']['username']
|
||||
);
|
||||
} else {
|
||||
$message = sprintf(
|
||||
t(
|
||||
'The database user "%s" will be used to create the missing database'
|
||||
. ' "%s" with the schema required by Icinga Web 2 and to grant'
|
||||
. ' access to it to a new login called "%s".'
|
||||
),
|
||||
$resourceConfig['username'],
|
||||
$resourceConfig['dbname'],
|
||||
$this->data['resourceConfig']['username']
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$message = sprintf(
|
||||
t(
|
||||
'The database user "%s" will be used to create the missing '
|
||||
. 'database "%s" with the schema required by Icinga Web 2.'
|
||||
'The database user "%s" will be used to create the missing'
|
||||
. ' database "%s" with the schema required by Icinga Web 2.'
|
||||
),
|
||||
$resourceConfig['username'],
|
||||
$resourceConfig['dbname']
|
||||
);
|
||||
} else {
|
||||
$message = sprintf(
|
||||
t(
|
||||
'The database user "%s" will be used to create the missing database "%s" '
|
||||
. 'with the schema required by Icinga Web 2 and a new login called "%s".'
|
||||
),
|
||||
$resourceConfig['username'],
|
||||
$resourceConfig['dbname'],
|
||||
$this->data['resourceConfig']['username']
|
||||
);
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
} catch (Exception $_) {
|
||||
$message = t(
|
||||
'No connection to database host possible. You\'ll need to setup the'
|
||||
. ' database with the schema required by Icinga Web 2 manually.'
|
||||
|
|
|
@ -38,6 +38,83 @@ class DbTool
|
|||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* Whether we are connected to the database from the resource configuration
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $dbFromConfig = false;
|
||||
|
||||
/**
|
||||
* GRANT privilege level identifiers
|
||||
*/
|
||||
const GLOBAL_LEVEL = 1;
|
||||
const PROCEDURE_LEVEL = 2;
|
||||
const DATABASE_LEVEL = 4;
|
||||
const TABLE_LEVEL = 8;
|
||||
const COLUMN_LEVEL = 16;
|
||||
const FUNCTION_LEVEL = 32;
|
||||
|
||||
/**
|
||||
* All MySQL GRANT privileges with their respective level identifiers
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $mysqlGrantContexts = array(
|
||||
'ALL' => 31,
|
||||
'ALL PRIVILEGES' => 31,
|
||||
'ALTER' => 13,
|
||||
'ALTER ROUTINE' => 7,
|
||||
'CREATE' => 13,
|
||||
'CREATE ROUTINE' => 5,
|
||||
'CREATE TEMPORARY TABLES' => 5,
|
||||
'CREATE USER' => 1,
|
||||
'CREATE VIEW' => 13,
|
||||
'DELETE' => 13,
|
||||
'DROP' => 13,
|
||||
'EXECUTE' => 5, // MySQL reference states this also supports database level, 5.1.73 not though
|
||||
'FILE' => 1,
|
||||
'GRANT OPTION' => 15,
|
||||
'INDEX' => 13,
|
||||
'INSERT' => 29,
|
||||
'LOCK TABLES' => 5,
|
||||
'PROCESS' => 1,
|
||||
'REFERENCES' => 0,
|
||||
'RELOAD' => 1,
|
||||
'REPLICATION CLIENT' => 1,
|
||||
'REPLICATION SLAVE' => 1,
|
||||
'SELECT' => 29,
|
||||
'SHOW DATABASES' => 1,
|
||||
'SHOW VIEW' => 13,
|
||||
'SHUTDOWN' => 1,
|
||||
'SUPER' => 1,
|
||||
'UPDATE' => 29
|
||||
);
|
||||
|
||||
/**
|
||||
* All PostgreSQL GRANT privileges with their respective level identifiers
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $pgsqlGrantContexts = array(
|
||||
'ALL' => 63,
|
||||
'ALL PRIVILEGES' => 63,
|
||||
'SELECT' => 24,
|
||||
'INSERT' => 24,
|
||||
'UPDATE' => 24,
|
||||
'DELETE' => 8,
|
||||
'TRUNCATE' => 8,
|
||||
'REFERENCES' => 24,
|
||||
'TRIGGER' => 8,
|
||||
'CREATE' => 12,
|
||||
'CONNECT' => 4,
|
||||
'TEMPORARY' => 4,
|
||||
'TEMP' => 4,
|
||||
'EXECUTE' => 32,
|
||||
'USAGE' => 33,
|
||||
'CREATEROLE' => 1
|
||||
);
|
||||
|
||||
/**
|
||||
* Create a new DbTool
|
||||
*
|
||||
|
@ -62,11 +139,12 @@ class DbTool
|
|||
// the current user name as default database in cases none is provided. If
|
||||
// that database doesn't exist (which might be the case here) it will error.
|
||||
// Therefore, we specify the maintenance database 'postgres' as database, which
|
||||
// is most probably present and public.
|
||||
// is most probably present and public. (http://stackoverflow.com/q/4483139)
|
||||
$this->connect('postgres');
|
||||
} else {
|
||||
$this->connect();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -137,6 +215,7 @@ class DbTool
|
|||
$this->_pdoConnect($dbname);
|
||||
if ($dbname !== null) {
|
||||
$this->_zendConnect($dbname);
|
||||
$this->dbFromConfig = $dbname === $this->config['dbname'];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -282,12 +361,12 @@ class DbTool
|
|||
*/
|
||||
public function quote($value)
|
||||
{
|
||||
$value = $this->pdoConn->quote($value);
|
||||
if ($value === false) {
|
||||
throw new LogicException('Unable to quote value');
|
||||
$quoted = $this->pdoConn->quote($value);
|
||||
if ($quoted === false) {
|
||||
throw new LogicException(sprintf('Unable to quote value: %s', $value));
|
||||
}
|
||||
|
||||
return $value;
|
||||
return $quoted;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -346,7 +425,7 @@ class DbTool
|
|||
$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) {
|
||||
foreach (preg_split('@;(?! \\\\)@', $content) as $statement) {
|
||||
if (($statement = trim($statement)) !== '') {
|
||||
$this->exec($statement);
|
||||
}
|
||||
|
@ -357,15 +436,108 @@ class DbTool
|
|||
* Return whether the given privileges were granted
|
||||
*
|
||||
* @param array $privileges An array of strings with the required privilege names
|
||||
* @param array $context An array describing the context for which the given privileges need to apply.
|
||||
* Only one or more table names are currently supported
|
||||
* @param string $username The login name for which to check the privileges,
|
||||
* if NULL the current login is used
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function checkPrivileges(array $privileges)
|
||||
public function checkPrivileges(array $privileges, array $context = null, $username = null)
|
||||
{
|
||||
if ($this->config['db'] === 'mysql') {
|
||||
return $this->checkMysqlPriv($privileges);
|
||||
} else {
|
||||
return $this->checkPgsqlPriv($privileges, $table);
|
||||
return $this->checkMysqlPrivileges($privileges, false, $context, $username);
|
||||
} elseif ($this->config['db'] === 'pgsql') {
|
||||
return $this->checkPgsqlPrivileges($privileges, false, $context, $username);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the given privileges are grantable to other users
|
||||
*
|
||||
* @param array $privileges The privileges that should be grantable
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isGrantable($privileges)
|
||||
{
|
||||
if ($this->config['db'] === 'mysql') {
|
||||
return $this->checkMysqlPrivileges($privileges, true);
|
||||
} elseif ($this->config['db'] === 'pgsql') {
|
||||
return $this->checkPgsqlPrivileges($privileges, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Grant all given privileges to the given user
|
||||
*
|
||||
* @param array $privileges The privilege names to grant
|
||||
* @param array $context An array describing the context for which the given privileges need to apply.
|
||||
* Only one or more table names are currently supported
|
||||
* @param string $username The username to grant the privileges to
|
||||
*/
|
||||
public function grantPrivileges(array $privileges, array $context, $username)
|
||||
{
|
||||
if ($this->config['db'] === 'mysql') {
|
||||
list($_, $host) = explode('@', $this->query('select current_user()')->fetchColumn());
|
||||
$queryString = sprintf(
|
||||
'GRANT %%s ON %s.%%s TO %s@%s',
|
||||
$this->quoteIdentifier($this->config['dbname']),
|
||||
$this->quoteIdentifier($username),
|
||||
$this->quoteIdentifier($host)
|
||||
);
|
||||
|
||||
$dbPrivileges = array();
|
||||
$tablePrivileges = array();
|
||||
foreach (array_intersect($privileges, array_keys($this->mysqlGrantContexts)) as $privilege) {
|
||||
if (false === empty($context) && $this->mysqlGrantContexts[$privilege] & static::TABLE_LEVEL) {
|
||||
$tablePrivileges[] = $privilege;
|
||||
} elseif ($this->mysqlGrantContexts[$privilege] & static::DATABASE_LEVEL) {
|
||||
$dbPrivileges[] = $privilege;
|
||||
}
|
||||
}
|
||||
|
||||
if (false === empty($tablePrivileges)) {
|
||||
foreach ($context as $table) {
|
||||
$this->exec(
|
||||
sprintf($queryString, join(',', $tablePrivileges), $this->quoteIdentifier($table))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (false === empty($dbPrivileges)) {
|
||||
$this->exec(sprintf($queryString, join(',', $dbPrivileges), '*'));
|
||||
}
|
||||
} elseif ($this->config['db'] === 'pgsql') {
|
||||
$dbPrivileges = array();
|
||||
$tablePrivileges = array();
|
||||
foreach (array_intersect($privileges, array_keys($this->pgsqlGrantContexts)) as $privilege) {
|
||||
if (false === empty($context) && $this->pgsqlGrantContexts[$privilege] & static::TABLE_LEVEL) {
|
||||
$tablePrivileges[] = $privilege;
|
||||
} elseif ($this->pgsqlGrantContexts[$privilege] & static::DATABASE_LEVEL) {
|
||||
$dbPrivileges[] = $privilege;
|
||||
}
|
||||
}
|
||||
|
||||
if (false === empty($dbPrivileges)) {
|
||||
$this->exec(sprintf(
|
||||
'GRANT %s ON DATABASE %s TO %s',
|
||||
join(',', $dbPrivileges),
|
||||
$this->config['dbname'],
|
||||
$username
|
||||
));
|
||||
}
|
||||
|
||||
if (false === empty($tablePrivileges)) {
|
||||
foreach ($context as $table) {
|
||||
$this->exec(sprintf(
|
||||
'GRANT %s ON TABLE %s TO %s',
|
||||
join(',', $tablePrivileges),
|
||||
$table,
|
||||
$username
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -384,33 +556,33 @@ class DbTool
|
|||
* Return whether the given database login exists
|
||||
*
|
||||
* @param string $username The username to search
|
||||
* @param string $password The password for user $username, required in case it's a MySQL database
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasLogin($username, $password = null)
|
||||
public function hasLogin($username)
|
||||
{
|
||||
if ($this->config['db'] === 'mysql') {
|
||||
// probe login by trial and error since we don't know our host name or it may be globbed
|
||||
try {
|
||||
$probeConf = $this->config;
|
||||
$probeConf['username'] = $username;
|
||||
$probeConf['password'] = $password;
|
||||
$probe = new DbTool($probeConf);
|
||||
$probe->connectToHost();
|
||||
} catch (PDOException $e) {
|
||||
return false;
|
||||
}
|
||||
$queryString = <<<EOD
|
||||
SELECT 1
|
||||
FROM information_schema.user_privileges
|
||||
WHERE grantee = REPLACE(CONCAT("'", REPLACE(CURRENT_USER(), '@', "'@'"), "'"), :current, :wanted)
|
||||
EOD;
|
||||
|
||||
return true;
|
||||
$query = $this->query(
|
||||
$queryString,
|
||||
array(
|
||||
':current' => $this->config['username'],
|
||||
':wanted' => $username
|
||||
)
|
||||
);
|
||||
return count($query->fetchAll()) > 0;
|
||||
} elseif ($this->config['db'] === 'pgsql') {
|
||||
$rowCount = $this->exec(
|
||||
'SELECT usename FROM pg_catalog.pg_user WHERE usename = :ident LIMIT 1',
|
||||
$query = $this->query(
|
||||
'SELECT 1 FROM pg_catalog.pg_user WHERE usename = :ident LIMIT 1',
|
||||
array(':ident' => $username)
|
||||
);
|
||||
return count($query->fetchAll()) === 1;
|
||||
}
|
||||
|
||||
return $rowCount === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -437,139 +609,169 @@ class DbTool
|
|||
}
|
||||
|
||||
/**
|
||||
* Check whether the current role has GRANT permissions
|
||||
* Check whether the current user has the given privileges
|
||||
*
|
||||
* @param array $privileges
|
||||
* @param $database
|
||||
* @param array $privileges The privilege names
|
||||
* @param bool $requireGrants Only return true when all privileges can be granted to others
|
||||
* @param array $context An array describing the context for which the given privileges need to apply.
|
||||
* Only one or more table names are currently supported
|
||||
* @param string $username The login name to which the passed privileges need to be granted
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function checkMysqlGrantOption(array $privileges)
|
||||
{
|
||||
return $this->checkMysqlPriv($privileges, true);
|
||||
}
|
||||
protected function checkMysqlPrivileges(
|
||||
array $privileges,
|
||||
$requireGrants = false,
|
||||
array $context = null,
|
||||
$username = null
|
||||
) {
|
||||
list($_, $host) = explode('@', $this->query('select current_user()')->fetchColumn());
|
||||
$grantee = "'" . ($username === null ? $this->config['username'] : $username) . "'@'" . $host . "'";
|
||||
$privilegeCondition = 'privilege_type IN (' . join(',', array_map(array($this, 'quote'), $privileges)) . ')';
|
||||
|
||||
/**
|
||||
* Check whether the current user has the given global privileges
|
||||
*
|
||||
* @param array $privileges The privilege names
|
||||
* @param boolean $requireGrants Only return true when all privileges can be granted to others
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function checkMysqlPriv(array $privileges, $requireGrants = false)
|
||||
{
|
||||
$cnt = count($privileges);
|
||||
if ($cnt <= 0) {
|
||||
return true;
|
||||
}
|
||||
$grantOption = '';
|
||||
if ($requireGrants) {
|
||||
$grantOption = ' AND IS_GRANTABLE = \'YES\'';
|
||||
}
|
||||
$rows = $this->exec(
|
||||
'SELECT PRIVILEGE_TYPE FROM information_schema.user_privileges ' .
|
||||
' WHERE GRANTEE = CONCAT("\'", REPLACE(CURRENT_USER(), \'@\', "\'@\'"), "\'") ' .
|
||||
' AND PRIVILEGE_TYPE IN (?' . str_repeat(',?', $cnt - 1) . ') ' . $grantOption . ';',
|
||||
$privileges
|
||||
);
|
||||
if (isset($this->config['dbname'])) {
|
||||
$dbPrivileges = array();
|
||||
$tablePrivileges = array();
|
||||
foreach (array_intersect($privileges, array_keys($this->mysqlGrantContexts)) as $privilege) {
|
||||
if (false === empty($context) && $this->mysqlGrantContexts[$privilege] & static::TABLE_LEVEL) {
|
||||
$tablePrivileges[] = $privilege;
|
||||
} elseif ($this->mysqlGrantContexts[$privilege] & static::DATABASE_LEVEL) {
|
||||
$dbPrivileges[] = $privilege;
|
||||
}
|
||||
}
|
||||
|
||||
return $cnt === $rows;
|
||||
}
|
||||
$dbPrivilegesGranted = true;
|
||||
if (false === empty($dbPrivileges)) {
|
||||
$query = $this->query(
|
||||
'SELECT COUNT(*) as matches'
|
||||
. ' FROM information_schema.schema_privileges'
|
||||
. ' WHERE grantee = :grantee'
|
||||
. ' AND table_schema = :dbname'
|
||||
. ' AND ' . $privilegeCondition
|
||||
. ($requireGrants ? " AND is_grantable = 'YES'" : ''),
|
||||
array(':grantee' => $grantee, ':dbname' => $this->config['dbname'])
|
||||
);
|
||||
$dbPrivilegesGranted = (int) $query->fetchObject()->matches === count($dbPrivileges);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current role has GRANT permissions for the given database name
|
||||
*
|
||||
* For Postgres, this will be assumed as true when:
|
||||
* <ul>
|
||||
* <li>The role can create new databases and the database does <b>not</b> yet exist </li>
|
||||
* <li>The database exists but the current role is the owner of it</li>
|
||||
* <li>The database exists but the role has superuser permissions</li>
|
||||
* <li>The role does not own the database, but has the necessary grants on it</li>
|
||||
* </ul>
|
||||
* A more fine-grained check of schema, table and columns permissions in the database
|
||||
* will not happen.
|
||||
*
|
||||
* @param array $privileges
|
||||
* @param $database The database
|
||||
* @param $table The optional table
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function checkPgsqlGrantOption(array $privileges, $database, $table = null)
|
||||
{
|
||||
if ($this->checkPgsqlPriv(array('SUPER'))) {
|
||||
// superuser
|
||||
return true;
|
||||
}
|
||||
$create = $this->checkPgsqlPriv(array('CREATE', 'CREATE USER'));
|
||||
$owner = $this->query(sprintf(
|
||||
'SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_database WHERE datname = %s',
|
||||
$this->quote($database)
|
||||
))->fetchColumn();
|
||||
if ($owner !== false) {
|
||||
if ($owner !== $this->config['username']) {
|
||||
// database already exists and the user is not owner of the database
|
||||
return $this->checkPgsqlPriv($privileges, $table, true);
|
||||
} else {
|
||||
// database already exists and the user is owner of the database
|
||||
$tablePrivilegesGranted = true;
|
||||
if (false === empty($tablePrivileges)) {
|
||||
$tableCondition = 'table_name IN (' . join(',', array_map(array($this, 'quote'), $context)) . ')';
|
||||
$query = $this->query(
|
||||
'SELECT COUNT(*) as matches'
|
||||
. ' FROM information_schema.table_privileges'
|
||||
. ' WHERE grantee = :grantee'
|
||||
. ' AND table_schema = :dbname'
|
||||
. ' AND ' . $tableCondition
|
||||
. ' AND ' . $privilegeCondition
|
||||
. ($requireGrants ? " AND is_grantable = 'YES'" : ''),
|
||||
array(':grantee' => $grantee, ':dbname' => $this->config['dbname'])
|
||||
);
|
||||
$expectedAmountOfMatches = count($context) * count($tablePrivileges);
|
||||
$tablePrivilegesGranted = (int) $query->fetchObject()->matches === $expectedAmountOfMatches;
|
||||
}
|
||||
|
||||
if ($dbPrivilegesGranted && $tablePrivilegesGranted) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// database does not exist, permission depends on createdb and createrole permissions
|
||||
return $create;
|
||||
|
||||
$query = $this->query(
|
||||
'SELECT COUNT(*) as matches FROM information_schema.user_privileges WHERE grantee = :grantee'
|
||||
. ' AND ' . $privilegeCondition . ($requireGrants ? " AND is_grantable = 'YES'" : ''),
|
||||
array(':grantee' => $grantee)
|
||||
);
|
||||
return (int) $query->fetchObject()->matches === count($privileges);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current role has the given privileges
|
||||
* Check whether the current user has the given privileges
|
||||
*
|
||||
* NOTE: The only global role privileges in Postgres are SUPER (superuser), CREATE and CREATE USER
|
||||
* (databases and roles), all others will be ignored in case no table was given
|
||||
* Note that database and table specific privileges (i.e. not SUPER, CREATE and CREATEROLE) are ignored
|
||||
* in case no connection to the database defined in the resource configuration has been established
|
||||
*
|
||||
* @param array $privileges The privileges to check
|
||||
* @param $table The optional schema to use, defaults to 'public'
|
||||
* @param $withGrant Whether we also require the grant option on the given privileges
|
||||
* @param array $privileges The privilege names
|
||||
* @param bool $requireGrants Only return true when all privileges can be granted to others
|
||||
* @param array $context An array describing the context for which the given privileges need to apply.
|
||||
* Only one or more table names are currently supported
|
||||
* @param string $username The login name to which the passed privileges need to be granted
|
||||
*
|
||||
* @return bool
|
||||
* @return bool
|
||||
*/
|
||||
public function checkPgsqlPriv(array $privileges, $table = null, $withGrantOption = false)
|
||||
{
|
||||
if (isset($table)) {
|
||||
$queries = array();
|
||||
foreach ($privileges as $privilege) {
|
||||
if (false === array_search($privilege, array('CREATE USER', 'CREATE', 'SUPER'))) {
|
||||
$queries[] = sprintf (
|
||||
'has_table_privilege(%s, %s)',
|
||||
$this->quote($table),
|
||||
$this->quote($privilege . ($withGrantOption ? ' WITH GRANT OPTION' : ''))
|
||||
) . ' AS ' . $this->quoteIdentifier($privilege);
|
||||
public function checkPgsqlPrivileges(
|
||||
array $privileges,
|
||||
$requireGrants = false,
|
||||
array $context = null,
|
||||
$username = null
|
||||
) {
|
||||
$privilegesGranted = true;
|
||||
if ($this->dbFromConfig) {
|
||||
$dbPrivileges = array();
|
||||
$tablePrivileges = array();
|
||||
foreach (array_intersect($privileges, array_keys($this->pgsqlGrantContexts)) as $privilege) {
|
||||
if (false === empty($context) && $this->pgsqlGrantContexts[$privilege] & static::TABLE_LEVEL) {
|
||||
$tablePrivileges[] = $privilege;
|
||||
} elseif ($this->pgsqlGrantContexts[$privilege] & static::DATABASE_LEVEL) {
|
||||
$dbPrivileges[] = $privilege;
|
||||
}
|
||||
}
|
||||
$ret = $this->query('SELECT ' . join (', ', $queries) . ';')->fetch();
|
||||
if (false === $ret || false !== array_search(false, $ret)) {
|
||||
return false;
|
||||
|
||||
if (false === empty($dbPrivileges)) {
|
||||
$query = $this->query(
|
||||
'SELECT has_database_privilege(:user, :dbname, :privileges) AS db_privileges_granted',
|
||||
array(
|
||||
':user' => $username !== null ? $username : $this->config['username'],
|
||||
':dbname' => $this->config['dbname'],
|
||||
':privileges' => join(',', $dbPrivileges) . ($requireGrants ? ' WITH GRANT OPTION' : '')
|
||||
)
|
||||
);
|
||||
$privilegesGranted &= $query->fetchObject()->db_privileges_granted;
|
||||
}
|
||||
}
|
||||
if (false !== array_search('CREATE USER', $privileges)) {
|
||||
$query = $this->query('select rolcreaterole from pg_roles where rolname = current_user;');
|
||||
$createrole = $query->fetchColumn();
|
||||
if (false === $createrole) {
|
||||
return false;
|
||||
|
||||
if (false === empty($tablePrivileges)) {
|
||||
foreach (array_intersect($context, $this->listTables()) as $table) {
|
||||
$query = $this->query(
|
||||
'SELECT has_table_privilege(:user, :table, :privileges) AS table_privileges_granted',
|
||||
array(
|
||||
':user' => $username !== null ? $username : $this->config['username'],
|
||||
':table' => $table,
|
||||
':privileges' => join(',', $tablePrivileges) . ($requireGrants ? ' WITH GRANT OPTION' : '')
|
||||
)
|
||||
);
|
||||
$privilegesGranted &= $query->fetchObject()->table_privileges_granted;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// In case we cannot check whether the user got the required db-/table-privileges due to not being
|
||||
// connected to the database defined in the resource configuration it is safe to just ignore them
|
||||
// as the chances are very high that the database is created later causing the current user being
|
||||
// the owner with ALL privileges. (Which in turn can be granted to others.)
|
||||
}
|
||||
|
||||
if (false !== array_search('CREATE', $privileges)) {
|
||||
$query = $this->query('select rolcreatedb from pg_roles where rolname = current_user;');
|
||||
$createdb = $query->fetchColumn();
|
||||
if (false === $createdb) {
|
||||
return false;
|
||||
}
|
||||
if (array_search('CREATE', $privileges) !== false) {
|
||||
$query = $this->query(
|
||||
'select rolcreatedb from pg_roles where rolname = :user',
|
||||
array(':user' => $username !== null ? $username : $this->config['username'])
|
||||
);
|
||||
$privilegesGranted &= $query->fetchColumn() !== false;
|
||||
}
|
||||
if (false !== array_search('SUPER', $privileges)) {
|
||||
$query = $this->query('select rolsuper from pg_roles where rolname = current_user;');
|
||||
$super = $query->fetchColumn();
|
||||
if (false === $super) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (array_search('CREATEROLE', $privileges) !== false) {
|
||||
$query = $this->query(
|
||||
'select rolcreaterole from pg_roles where rolname = :user',
|
||||
array(':user' => $username !== null ? $username : $this->config['username'])
|
||||
);
|
||||
$privilegesGranted &= $query->fetchColumn() !== false;
|
||||
}
|
||||
return true;
|
||||
|
||||
if (array_search('SUPER', $privileges) !== false) {
|
||||
$query = $this->query(
|
||||
'select rolsuper from pg_roles where rolname = :user',
|
||||
array(':user' => $username !== null ? $username : $this->config['username'])
|
||||
);
|
||||
$privilegesGranted &= $query->fetchColumn() !== false;
|
||||
}
|
||||
|
||||
return $privilegesGranted;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue