From 9c6fda7b336cc7c02a9cef66f67ebe073978c8cf Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Fri, 29 Apr 2022 15:01:46 +0200 Subject: [PATCH 01/16] Remove `IniStore.php` --- .../User/Preferences/Store/IniStore.php | 118 ------------------ 1 file changed, 118 deletions(-) delete mode 100644 library/Icinga/User/Preferences/Store/IniStore.php diff --git a/library/Icinga/User/Preferences/Store/IniStore.php b/library/Icinga/User/Preferences/Store/IniStore.php deleted file mode 100644 index f456f141a..000000000 --- a/library/Icinga/User/Preferences/Store/IniStore.php +++ /dev/null @@ -1,118 +0,0 @@ -preferencesFile = sprintf( - '%s/%s/config.ini', - $this->getStoreConfig()->location, - strtolower($this->getUser()->getUsername()) - ); - } - - /** - * Load preferences from source - * - * @return array - * - * @throws NotReadableError When the INI file of the user exists and is not readable - */ - public function load() - { - if (file_exists($this->preferencesFile)) { - if (! is_readable($this->preferencesFile)) { - throw new NotReadableError( - 'Preferences INI file %s for user %s is not readable', - $this->preferencesFile, - $this->getUser()->getUsername() - ); - } else { - $this->preferences = IniParser::parseIniFile($this->preferencesFile)->toArray(); - } - } - - return $this->preferences; - } - - /** - * Save the given preferences - * - * @param Preferences $preferences The preferences to save - */ - public function save(Preferences $preferences) - { - $this->preferences = $preferences->toArray(); - - // TODO: Elaborate whether we need to patch the contents - // $preferences = $preferences->toArray(); - // $this->update(array_diff_assoc($preferences, $this->preferences)); - // $this->delete(array_keys(array_diff_key($this->preferences, $preferences))); - - $this->write(); - } - - /** - * Write the preferences - * - * @throws NotWritableError In case the INI file cannot be written - */ - public function write() - { - Config::fromArray($this->preferences)->saveIni($this->preferencesFile); - } - - /** - * Add or update the given preferences - * - * @param array $preferences The preferences to set - */ - protected function update(array $preferences) - { - foreach ($preferences as $key => $value) { - $this->preferences[$key] = $value; - } - } - - /** - * Delete the given preferences by name - * - * @param array $preferenceKeys The preference names to delete - */ - protected function delete(array $preferenceKeys) - { - foreach ($preferenceKeys as $key) { - unset($this->preferences[$key]); - } - } -} From 8ff1a22df797fab5b2b8e481ca6b8bd8a0d3b5f3 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Fri, 29 Apr 2022 15:11:48 +0200 Subject: [PATCH 02/16] Set preferences store type to `Db` and make it non-configurable --- application/controllers/AccountController.php | 1 - library/Icinga/Authentication/Auth.php | 1 - .../User/Preferences/PreferencesStore.php | 29 ++++--------------- 3 files changed, 6 insertions(+), 25 deletions(-) diff --git a/application/controllers/AccountController.php b/application/controllers/AccountController.php index 4dc655452..9852e1111 100644 --- a/application/controllers/AccountController.php +++ b/application/controllers/AccountController.php @@ -71,7 +71,6 @@ class AccountController extends Controller $form->setPreferences($user->getPreferences()); if ($config->get('config_backend', 'db') !== 'none' && isset($config->config_resource)) { $form->setStore(PreferencesStore::create(new ConfigObject(array( - 'store' => $config->get('config_backend', 'db'), 'resource' => $config->config_resource )), $user)); } diff --git a/library/Icinga/Authentication/Auth.php b/library/Icinga/Authentication/Auth.php index c02f75084..45d815bd9 100644 --- a/library/Icinga/Authentication/Auth.php +++ b/library/Icinga/Authentication/Auth.php @@ -378,7 +378,6 @@ class Auth if ($config->get('global', 'config_backend', 'db') !== 'none') { $preferencesConfig = new ConfigObject([ - 'store' => $config->get('global', 'config_backend', 'db'), 'resource' => $config->get('global', 'config_resource') ]); diff --git a/library/Icinga/User/Preferences/PreferencesStore.php b/library/Icinga/User/Preferences/PreferencesStore.php index d3c5f5397..f11bf5d0c 100644 --- a/library/Icinga/User/Preferences/PreferencesStore.php +++ b/library/Icinga/User/Preferences/PreferencesStore.php @@ -3,14 +3,12 @@ namespace Icinga\User\Preferences; -use Icinga\Application\Config; -use Icinga\Application\Logger; use Icinga\User; use Icinga\User\Preferences; use Icinga\Data\ConfigObject; use Icinga\Data\ResourceFactory; use Icinga\Exception\ConfigurationError; -use Icinga\Data\Db\DbConnection; +use Icinga\User\Preferences\Store\DbStore; /** * Preferences store factory @@ -26,7 +24,6 @@ use Icinga\Data\Db\DbConnection; * // Create a INI store * $store = PreferencesStore::create( * new ConfigObject( - * 'store' => 'ini', * 'config_path' => '/path/to/preferences' * ), * $user // Instance of \Icinga\User @@ -117,27 +114,13 @@ abstract class PreferencesStore */ public static function create(ConfigObject $config, User $user) { - $type = ucfirst(strtolower($config->get('store', 'db'))); - $storeClass = 'Icinga\\User\\Preferences\\Store\\' . $type . 'Store'; - if (!class_exists($storeClass)) { - throw new ConfigurationError( - 'Preferences configuration defines an invalid storage type. Storage type %s not found', - $type - ); + $resourceConfig = ResourceFactory::getResourceConfig($config->resource); + if ($resourceConfig->db === 'mysql') { + $resourceConfig->charset = 'utf8mb4'; } - if ($type === 'Ini') { - Logger::warning('The preferences backend of type INI is deprecated and will be removed with version 2.11'); - $config->location = Config::resolvePath('preferences'); - } elseif ($type === 'Db') { - $resourceConfig = ResourceFactory::getResourceConfig($config->resource); - if ($resourceConfig->db === 'mysql') { - $resourceConfig->charset = 'utf8mb4'; - } + $config->connection = ResourceFactory::createResource($resourceConfig); - $config->connection = ResourceFactory::createResource($resourceConfig); - } - - return new $storeClass($config, $user); + return new DbStore($config, $user); } } From 566f5db8cf048a3da89236c7ac8d4c91dab579e5 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Fri, 29 Apr 2022 15:21:59 +0200 Subject: [PATCH 03/16] Add all `DbStore.php` class code to `PreferencesStore.php` Remove `DbStore.php` Since all user preferences must be stored in the database, it is not necessary to have a child class. --- .../User/Preferences/PreferencesStore.php | 244 ++++++++++++++++- .../Icinga/User/Preferences/Store/DbStore.php | 255 ------------------ 2 files changed, 234 insertions(+), 265 deletions(-) delete mode 100644 library/Icinga/User/Preferences/Store/DbStore.php diff --git a/library/Icinga/User/Preferences/PreferencesStore.php b/library/Icinga/User/Preferences/PreferencesStore.php index f11bf5d0c..44b407302 100644 --- a/library/Icinga/User/Preferences/PreferencesStore.php +++ b/library/Icinga/User/Preferences/PreferencesStore.php @@ -3,16 +3,21 @@ namespace Icinga\User\Preferences; +use Exception; +use Icinga\Exception\NotReadableError; +use Icinga\Exception\NotWritableError; use Icinga\User; use Icinga\User\Preferences; use Icinga\Data\ConfigObject; use Icinga\Data\ResourceFactory; use Icinga\Exception\ConfigurationError; -use Icinga\User\Preferences\Store\DbStore; +use Zend_Db_Expr; /** * Preferences store factory * + * Load and save user preferences by using a database + * * Usage example: * * '/path/to/preferences' + * 'resource' => 'resource name' * ), * $user // Instance of \Icinga\User * ); @@ -34,8 +39,52 @@ use Icinga\User\Preferences\Store\DbStore; * $store->save($preferences); * */ -abstract class PreferencesStore +class PreferencesStore { + /** + * Column name for username + */ + const COLUMN_USERNAME = 'username'; + + /** + * Column name for section + */ + const COLUMN_SECTION = 'section'; + + /** + * Column name for preference + */ + const COLUMN_PREFERENCE = 'name'; + + /** + * Column name for value + */ + const COLUMN_VALUE = 'value'; + + /** + * Column name for created time + */ + const COLUMN_CREATED_TIME = 'ctime'; + + /** + * Column name for modified time + */ + const COLUMN_MODIFIED_TIME = 'mtime'; + + /** + * Table name + * + * @var string + */ + protected $table = 'icingaweb_user_preference'; + + /** + * Stored preferences + * + * @var array + */ + protected $preferences = array(); + /** * Store config * @@ -83,24 +132,199 @@ abstract class PreferencesStore return $this->user; } + /** + * Set the table to use + * + * @param string $table The table name + */ + public function setTable($table) + { + $this->table = $table; + } + /** * Initialize the store */ - abstract protected function init(); + protected function init() + { + } /** - * Load preferences from source + * Load preferences from the database * * @return array + * + * @throws NotReadableError In case the database operation failed */ - abstract public function load(); + public function load() + { + try { + $select = $this->getStoreConfig()->connection->getDbAdapter()->select(); + $result = $select + ->from($this->table, array(self::COLUMN_SECTION, self::COLUMN_PREFERENCE, self::COLUMN_VALUE)) + ->where(self::COLUMN_USERNAME . ' = ?', $this->getUser()->getUsername()) + ->query() + ->fetchAll(); + } catch (Exception $e) { + throw new NotReadableError( + 'Cannot fetch preferences for user %s from database', + $this->getUser()->getUsername(), + $e + ); + } + + if ($result !== false) { + $values = array(); + foreach ($result as $row) { + $values[$row->{self::COLUMN_SECTION}][$row->{self::COLUMN_PREFERENCE}] = $row->{self::COLUMN_VALUE}; + } + $this->preferences = $values; + } + + return $this->preferences; + } /** - * Save the given preferences + * Save the given preferences in the database * * @param Preferences $preferences The preferences to save */ - abstract public function save(Preferences $preferences); + public function save(Preferences $preferences) + { + $preferences = $preferences->toArray(); + + $sections = array_keys($preferences); + + foreach ($sections as $section) { + if (! array_key_exists($section, $this->preferences)) { + $this->preferences[$section] = array(); + } + if (! array_key_exists($section, $preferences)) { + $preferences[$section] = array(); + } + $toBeInserted = array_diff_key($preferences[$section], $this->preferences[$section]); + if (!empty($toBeInserted)) { + $this->insert($toBeInserted, $section); + } + + $toBeUpdated = array_intersect_key( + array_diff_assoc($preferences[$section], $this->preferences[$section]), + array_diff_assoc($this->preferences[$section], $preferences[$section]) + ); + if (!empty($toBeUpdated)) { + $this->update($toBeUpdated, $section); + } + + $toBeDeleted = array_keys(array_diff_key($this->preferences[$section], $preferences[$section])); + if (!empty($toBeDeleted)) { + $this->delete($toBeDeleted, $section); + } + } + } + + /** + * Insert the given preferences into the database + * + * @param array $preferences The preferences to insert + * @param string $section The preferences in section to update + * + * @throws NotWritableError In case the database operation failed + */ + protected function insert(array $preferences, $section) + { + /** @var \Zend_Db_Adapter_Abstract $db */ + $db = $this->getStoreConfig()->connection->getDbAdapter(); + + try { + foreach ($preferences as $key => $value) { + $db->insert( + $this->table, + array( + self::COLUMN_USERNAME => $this->getUser()->getUsername(), + $db->quoteIdentifier(self::COLUMN_SECTION) => $section, + $db->quoteIdentifier(self::COLUMN_PREFERENCE) => $key, + self::COLUMN_VALUE => $value, + self::COLUMN_CREATED_TIME => new Zend_Db_Expr('NOW()'), + self::COLUMN_MODIFIED_TIME => new Zend_Db_Expr('NOW()') + ) + ); + } + } catch (Exception $e) { + throw new NotWritableError( + 'Cannot insert preferences for user %s into database', + $this->getUser()->getUsername(), + $e + ); + } + } + + /** + * Update the given preferences in the database + * + * @param array $preferences The preferences to update + * @param string $section The preferences in section to update + * + * @throws NotWritableError In case the database operation failed + */ + protected function update(array $preferences, $section) + { + /** @var \Zend_Db_Adapter_Abstract $db */ + $db = $this->getStoreConfig()->connection->getDbAdapter(); + + try { + foreach ($preferences as $key => $value) { + $db->update( + $this->table, + array( + self::COLUMN_VALUE => $value, + self::COLUMN_MODIFIED_TIME => new Zend_Db_Expr('NOW()') + ), + array( + self::COLUMN_USERNAME . '=?' => $this->getUser()->getUsername(), + $db->quoteIdentifier(self::COLUMN_SECTION) . '=?' => $section, + $db->quoteIdentifier(self::COLUMN_PREFERENCE) . '=?' => $key + ) + ); + } + } catch (Exception $e) { + throw new NotWritableError( + 'Cannot update preferences for user %s in database', + $this->getUser()->getUsername(), + $e + ); + } + } + + /** + * Delete the given preference names from the database + * + * @param array $preferenceKeys The preference names to delete + * @param string $section The preferences in section to update + * + * @throws NotWritableError In case the database operation failed + */ + protected function delete(array $preferenceKeys, $section) + { + /** @var \Zend_Db_Adapter_Abstract $db */ + $db = $this->getStoreConfig()->connection->getDbAdapter(); + + try { + $db->delete( + $this->table, + array( + self::COLUMN_USERNAME . '=?' => $this->getUser()->getUsername(), + $db->quoteIdentifier(self::COLUMN_SECTION) . '=?' => $section, + $db->quoteIdentifier(self::COLUMN_PREFERENCE) . ' IN (?)' => $preferenceKeys + ) + ); + } catch (Exception $e) { + throw new NotWritableError( + 'Cannot delete preferences for user %s from database', + $this->getUser()->getUsername(), + $e + ); + } + } /** * Create preferences storage adapter from config @@ -121,6 +345,6 @@ abstract class PreferencesStore $config->connection = ResourceFactory::createResource($resourceConfig); - return new DbStore($config, $user); + return new self($config, $user); } } diff --git a/library/Icinga/User/Preferences/Store/DbStore.php b/library/Icinga/User/Preferences/Store/DbStore.php deleted file mode 100644 index 341a98358..000000000 --- a/library/Icinga/User/Preferences/Store/DbStore.php +++ /dev/null @@ -1,255 +0,0 @@ -table = $table; - } - - /** - * Initialize the store - */ - protected function init() - { - } - - /** - * Load preferences from the database - * - * @return array - * - * @throws NotReadableError In case the database operation failed - */ - public function load() - { - try { - $select = $this->getStoreConfig()->connection->getDbAdapter()->select(); - $result = $select - ->from($this->table, array(self::COLUMN_SECTION, self::COLUMN_PREFERENCE, self::COLUMN_VALUE)) - ->where(self::COLUMN_USERNAME . ' = ?', $this->getUser()->getUsername()) - ->query() - ->fetchAll(); - } catch (Exception $e) { - throw new NotReadableError( - 'Cannot fetch preferences for user %s from database', - $this->getUser()->getUsername(), - $e - ); - } - - if ($result !== false) { - $values = array(); - foreach ($result as $row) { - $values[$row->{self::COLUMN_SECTION}][$row->{self::COLUMN_PREFERENCE}] = $row->{self::COLUMN_VALUE}; - } - $this->preferences = $values; - } - - return $this->preferences; - } - - /** - * Save the given preferences in the database - * - * @param Preferences $preferences The preferences to save - */ - public function save(Preferences $preferences) - { - $preferences = $preferences->toArray(); - - $sections = array_keys($preferences); - - foreach ($sections as $section) { - if (! array_key_exists($section, $this->preferences)) { - $this->preferences[$section] = array(); - } - if (! array_key_exists($section, $preferences)) { - $preferences[$section] = array(); - } - $toBeInserted = array_diff_key($preferences[$section], $this->preferences[$section]); - if (!empty($toBeInserted)) { - $this->insert($toBeInserted, $section); - } - - $toBeUpdated = array_intersect_key( - array_diff_assoc($preferences[$section], $this->preferences[$section]), - array_diff_assoc($this->preferences[$section], $preferences[$section]) - ); - if (!empty($toBeUpdated)) { - $this->update($toBeUpdated, $section); - } - - $toBeDeleted = array_keys(array_diff_key($this->preferences[$section], $preferences[$section])); - if (!empty($toBeDeleted)) { - $this->delete($toBeDeleted, $section); - } - } - } - - /** - * Insert the given preferences into the database - * - * @param array $preferences The preferences to insert - * @param string $section The preferences in section to update - * - * @throws NotWritableError In case the database operation failed - */ - protected function insert(array $preferences, $section) - { - /** @var \Zend_Db_Adapter_Abstract $db */ - $db = $this->getStoreConfig()->connection->getDbAdapter(); - - try { - foreach ($preferences as $key => $value) { - $db->insert( - $this->table, - array( - self::COLUMN_USERNAME => $this->getUser()->getUsername(), - $db->quoteIdentifier(self::COLUMN_SECTION) => $section, - $db->quoteIdentifier(self::COLUMN_PREFERENCE) => $key, - self::COLUMN_VALUE => $value, - self::COLUMN_CREATED_TIME => new Zend_Db_Expr('NOW()'), - self::COLUMN_MODIFIED_TIME => new Zend_Db_Expr('NOW()') - ) - ); - } - } catch (Exception $e) { - throw new NotWritableError( - 'Cannot insert preferences for user %s into database', - $this->getUser()->getUsername(), - $e - ); - } - } - - /** - * Update the given preferences in the database - * - * @param array $preferences The preferences to update - * @param string $section The preferences in section to update - * - * @throws NotWritableError In case the database operation failed - */ - protected function update(array $preferences, $section) - { - /** @var \Zend_Db_Adapter_Abstract $db */ - $db = $this->getStoreConfig()->connection->getDbAdapter(); - - try { - foreach ($preferences as $key => $value) { - $db->update( - $this->table, - array( - self::COLUMN_VALUE => $value, - self::COLUMN_MODIFIED_TIME => new Zend_Db_Expr('NOW()') - ), - array( - self::COLUMN_USERNAME . '=?' => $this->getUser()->getUsername(), - $db->quoteIdentifier(self::COLUMN_SECTION) . '=?' => $section, - $db->quoteIdentifier(self::COLUMN_PREFERENCE) . '=?' => $key - ) - ); - } - } catch (Exception $e) { - throw new NotWritableError( - 'Cannot update preferences for user %s in database', - $this->getUser()->getUsername(), - $e - ); - } - } - - /** - * Delete the given preference names from the database - * - * @param array $preferenceKeys The preference names to delete - * @param string $section The preferences in section to update - * - * @throws NotWritableError In case the database operation failed - */ - protected function delete(array $preferenceKeys, $section) - { - /** @var \Zend_Db_Adapter_Abstract $db */ - $db = $this->getStoreConfig()->connection->getDbAdapter(); - - try { - $db->delete( - $this->table, - array( - self::COLUMN_USERNAME . '=?' => $this->getUser()->getUsername(), - $db->quoteIdentifier(self::COLUMN_SECTION) . '=?' => $section, - $db->quoteIdentifier(self::COLUMN_PREFERENCE) . ' IN (?)' => $preferenceKeys - ) - ); - } catch (Exception $e) { - throw new NotWritableError( - 'Cannot delete preferences for user %s from database', - $this->getUser()->getUsername(), - $e - ); - } - } -} From 213c60334a1bf5b1ed3e80bd177e72ac49758857 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Mon, 2 May 2022 13:57:33 +0200 Subject: [PATCH 04/16] Rename class `DbStoreTest` to `PreferencesStoreTest` --- ...StoreTest.php => PreferencesStoreTest.php} | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) rename test/php/library/Icinga/User/Store/{DbStoreTest.php => PreferencesStoreTest.php} (72%) diff --git a/test/php/library/Icinga/User/Store/DbStoreTest.php b/test/php/library/Icinga/User/Store/PreferencesStoreTest.php similarity index 72% rename from test/php/library/Icinga/User/Store/DbStoreTest.php rename to test/php/library/Icinga/User/Store/PreferencesStoreTest.php index 1f56f93cb..4ae8d712f 100644 --- a/test/php/library/Icinga/User/Store/DbStoreTest.php +++ b/test/php/library/Icinga/User/Store/PreferencesStoreTest.php @@ -1,13 +1,13 @@ insertions[$row[DbStore::COLUMN_PREFERENCE]] = $row[DbStore::COLUMN_VALUE]; + $this->insertions[$row[PreferencesStore::COLUMN_PREFERENCE]] = $row[PreferencesStore::COLUMN_VALUE]; } public function update($table, $columns, $where) { - $this->updates[$where[DbStore::COLUMN_PREFERENCE . '=?']] = $columns[DbStore::COLUMN_VALUE]; + $this->updates[$where[PreferencesStore::COLUMN_PREFERENCE . '=?']] = $columns[PreferencesStore::COLUMN_VALUE]; } public function delete($table, $where) { $this->deletions = array_merge( $this->deletions, - $where[DbStore::COLUMN_PREFERENCE . ' IN (?)'] + $where[PreferencesStore::COLUMN_PREFERENCE . ' IN (?)'] ); } } @@ -57,7 +57,7 @@ class FaultyDatabaseMock extends DatabaseMock } } -class DbStoreWithSetPreferences extends DbStore +class PreferencesStoreWithSetPreferences extends PreferencesStore { public function setPreferences(array $preferences) { @@ -65,7 +65,7 @@ class DbStoreWithSetPreferences extends DbStore } } -class DbStoreTest extends BaseTestCase +class PreferencesStoreTest extends BaseTestCase { public function testWhetherPreferenceInsertionWorks() { @@ -78,9 +78,9 @@ class DbStoreTest extends BaseTestCase ) ); - $this->assertArrayHasKey('key', $dbMock->insertions, 'DbStore::save does not insert new preferences'); - $this->assertEmpty($dbMock->updates, 'DbStore::save updates *new* preferences'); - $this->assertEmpty($dbMock->deletions, 'DbStore::save deletes *new* preferences'); + $this->assertArrayHasKey('key', $dbMock->insertions, 'PreferencesStore::save does not insert new preferences'); + $this->assertEmpty($dbMock->updates, 'PreferencesStore::save updates *new* preferences'); + $this->assertEmpty($dbMock->deletions, 'PreferencesStore::save deletes *new* preferences'); } public function testWhetherPreferenceInsertionThrowsNotWritableError() @@ -108,9 +108,9 @@ class DbStoreTest extends BaseTestCase ) ); - $this->assertArrayHasKey('key', $dbMock->updates, 'DbStore::save does not update existing preferences'); - $this->assertEmpty($dbMock->insertions, 'DbStore::save inserts *existing* preferences'); - $this->assertEmpty($dbMock->deletions, 'DbStore::save inserts *existing* preferneces'); + $this->assertArrayHasKey('key', $dbMock->updates, 'PreferencesStore::save does not update existing preferences'); + $this->assertEmpty($dbMock->insertions, 'PreferencesStore::save inserts *existing* preferences'); + $this->assertEmpty($dbMock->deletions, 'PreferencesStore::save inserts *existing* preferneces'); } public function testWhetherPreferenceUpdatesThrowNotWritableError() @@ -139,9 +139,9 @@ class DbStoreTest extends BaseTestCase ) ); - $this->assertContains('key', $dbMock->deletions, 'DbStore::save does not delete removed preferences'); - $this->assertEmpty($dbMock->insertions, 'DbStore::save inserts *removed* preferences'); - $this->assertEmpty($dbMock->updates, 'DbStore::save updates *removed* preferences'); + $this->assertContains('key', $dbMock->deletions, 'PreferencesStore::save does not delete removed preferences'); + $this->assertEmpty($dbMock->insertions, 'PreferencesStore::save inserts *removed* preferences'); + $this->assertEmpty($dbMock->updates, 'PreferencesStore::save updates *removed* preferences'); } public function testWhetherPreferenceDeletionThrowsNotWritableError() @@ -160,7 +160,7 @@ class DbStoreTest extends BaseTestCase protected function getStore($dbMock) { - return new DbStoreWithSetPreferences( + return new PreferencesStoreWithSetPreferences( new ConfigObject( array( 'connection' => Mockery::mock(array('getDbAdapter' => $dbMock)) From d8895669b36b25e4638afb2cbce280f7e5ed3d03 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Mon, 2 May 2022 14:00:18 +0200 Subject: [PATCH 05/16] Remove class `IniStoreTest` --- .../Icinga/User/Store/IniStoreTest.php | 71 ------------------- 1 file changed, 71 deletions(-) delete mode 100644 test/php/library/Icinga/User/Store/IniStoreTest.php diff --git a/test/php/library/Icinga/User/Store/IniStoreTest.php b/test/php/library/Icinga/User/Store/IniStoreTest.php deleted file mode 100644 index c5baef9c4..000000000 --- a/test/php/library/Icinga/User/Store/IniStoreTest.php +++ /dev/null @@ -1,71 +0,0 @@ -preferences = $preferences; - } - - public function getPreferences() - { - return $this->preferences; - } -} - -class IniStoreTest extends BaseTestCase -{ - public function testWhetherPreferenceChangesAreApplied() - { - $store = $this->getStore(); - $store->setPreferences(array('testsection' => array('key1' => '1'))); - - $store->save( - Mockery::mock('Icinga\User\Preferences', array( - 'toArray' => array('testsection' => array('key1' => '11', 'key2' => '2')) - )) - ); - $this->assertEquals( - array('testsection' => array('key1' => '11', 'key2' => '2')), - $store->getPreferences(), - 'IniStore::save does not properly apply changed preferences' - ); - } - - public function testWhetherPreferenceDeletionsAreApplied() - { - $store = $this->getStore(); - $store->setPreferences(array('testsection' => array('key' => 'value'))); - - $store->save(Mockery::mock('Icinga\User\Preferences', array('toArray' => array('testsection' => array())))); - - $result = $store->getPreferences(); - - $this->assertEmpty($result['testsection'], 'IniStore::save does not delete removed preferences'); - } - - protected function getStore() - { - return new IniStoreWithSetGetPreferencesAndEmptyWrite( - new ConfigObject( - array( - 'location' => 'some/Path/To/Some/Directory' - ) - ), - Mockery::mock('Icinga\User', array('getUsername' => 'unittest')) - ); - } -} From 18ecd444c484b0a2df3da8cf87c710ff9a5e8518 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Mon, 2 May 2022 14:04:27 +0200 Subject: [PATCH 06/16] Remove class `PreferencesCommand` --- .../clicommands/PreferencesCommand.php | 105 ------------------ 1 file changed, 105 deletions(-) delete mode 100644 modules/migrate/application/clicommands/PreferencesCommand.php diff --git a/modules/migrate/application/clicommands/PreferencesCommand.php b/modules/migrate/application/clicommands/PreferencesCommand.php deleted file mode 100644 index f31665493..000000000 --- a/modules/migrate/application/clicommands/PreferencesCommand.php +++ /dev/null @@ -1,105 +0,0 @@ - The resource to use, if no current database config backend is configured. - * --no-set-config-backend Do not set the given resource as config backend automatically - */ - public function indexAction() - { - $resource = Config::app()->get('global', 'config_resource'); - if (empty($resource)) { - $resource = $this->params->getRequired('resource'); - } - - $resourceConfig = ResourceFactory::getResourceConfig($resource); - if ($resourceConfig->db === 'mysql') { - $resourceConfig->charset = 'utf8mb4'; - } - - $connection = ResourceFactory::createResource($resourceConfig); - - $preferencesPath = Config::resolvePath('preferences'); - if (! file_exists($preferencesPath)) { - Logger::info('There are no local user preferences to migrate'); - return; - } - - $rc = 0; - - $preferenceDirs = new DirectoryIterator($preferencesPath); - foreach ($preferenceDirs as $preferenceDir) { - if (! is_dir($preferenceDir)) { - continue; - } - - $userName = basename($preferenceDir); - - Logger::info('Migrating INI preferences for user "%s" to database...', $userName); - - $iniStore = new IniStore(new ConfigObject(['location' => $preferencesPath]), new User($userName)); - $dbStore = new DbStore(new ConfigObject(['connection' => $connection]), new User($userName)); - - try { - $dbStore->load(); - $dbStore->save(new User\Preferences($iniStore->load())); - } catch (NotReadableError $e) { - if ($e->getPrevious() !== null) { - Logger::error('%s: %s', $e->getMessage(), $e->getPrevious()->getMessage()); - } else { - Logger::error($e->getMessage()); - } - - $rc = 128; - } catch (NotWritableError $e) { - Logger::error('%s: %s', $e->getMessage(), $e->getPrevious()->getMessage()); - $rc = 256; - } - } - - if ($rc > 0) { - Logger::error('Failed to migrate some user preferences'); - exit($rc); - } - - if ($this->params->has('resource') && ! $this->params->has('no-set-config-backend')) { - $appConfig = Config::app(); - $globalConfig = $appConfig->getSection('global'); - $globalConfig['config_backend'] = 'db'; - $globalConfig['config_resource'] = $resource; - - try { - $appConfig->saveIni(); - } catch (NotWritableError $e) { - Logger::error('Failed to update general configuration: %s', $e->getMessage()); - exit(256); - } - } - - Logger::info('Successfully migrated all local user preferences to database'); - } -} From fa7f100c037ade7280ad2f95c40f2da8fd850942 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Mon, 2 May 2022 14:20:04 +0200 Subject: [PATCH 07/16] PreferencesStore: Add new syntax --- .../User/Preferences/PreferencesStore.php | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/library/Icinga/User/Preferences/PreferencesStore.php b/library/Icinga/User/Preferences/PreferencesStore.php index 44b407302..0468b097c 100644 --- a/library/Icinga/User/Preferences/PreferencesStore.php +++ b/library/Icinga/User/Preferences/PreferencesStore.php @@ -117,7 +117,7 @@ class PreferencesStore * * @return ConfigObject */ - public function getStoreConfig() + public function getStoreConfig(): ConfigObject { return $this->config; } @@ -127,7 +127,7 @@ class PreferencesStore * * @return User */ - public function getUser() + public function getUser(): User { return $this->user; } @@ -137,7 +137,7 @@ class PreferencesStore * * @param string $table The table name */ - public function setTable($table) + public function setTable(string $table) { $this->table = $table; } @@ -145,7 +145,7 @@ class PreferencesStore /** * Initialize the store */ - protected function init() + protected function init(): void { } @@ -156,12 +156,12 @@ class PreferencesStore * * @throws NotReadableError In case the database operation failed */ - public function load() + public function load(): array { try { $select = $this->getStoreConfig()->connection->getDbAdapter()->select(); $result = $select - ->from($this->table, array(self::COLUMN_SECTION, self::COLUMN_PREFERENCE, self::COLUMN_VALUE)) + ->from($this->table, [self::COLUMN_SECTION, self::COLUMN_PREFERENCE, self::COLUMN_VALUE]) ->where(self::COLUMN_USERNAME . ' = ?', $this->getUser()->getUsername()) ->query() ->fetchAll(); @@ -174,7 +174,7 @@ class PreferencesStore } if ($result !== false) { - $values = array(); + $values = []; foreach ($result as $row) { $values[$row->{self::COLUMN_SECTION}][$row->{self::COLUMN_PREFERENCE}] = $row->{self::COLUMN_VALUE}; } @@ -189,7 +189,7 @@ class PreferencesStore * * @param Preferences $preferences The preferences to save */ - public function save(Preferences $preferences) + public function save(Preferences $preferences): void { $preferences = $preferences->toArray(); @@ -197,10 +197,10 @@ class PreferencesStore foreach ($sections as $section) { if (! array_key_exists($section, $this->preferences)) { - $this->preferences[$section] = array(); + $this->preferences[$section] = []; } if (! array_key_exists($section, $preferences)) { - $preferences[$section] = array(); + $preferences[$section] = []; } $toBeInserted = array_diff_key($preferences[$section], $this->preferences[$section]); if (!empty($toBeInserted)) { @@ -230,7 +230,7 @@ class PreferencesStore * * @throws NotWritableError In case the database operation failed */ - protected function insert(array $preferences, $section) + protected function insert(array $preferences, string $section): void { /** @var \Zend_Db_Adapter_Abstract $db */ $db = $this->getStoreConfig()->connection->getDbAdapter(); @@ -239,14 +239,14 @@ class PreferencesStore foreach ($preferences as $key => $value) { $db->insert( $this->table, - array( + [ self::COLUMN_USERNAME => $this->getUser()->getUsername(), $db->quoteIdentifier(self::COLUMN_SECTION) => $section, $db->quoteIdentifier(self::COLUMN_PREFERENCE) => $key, self::COLUMN_VALUE => $value, self::COLUMN_CREATED_TIME => new Zend_Db_Expr('NOW()'), self::COLUMN_MODIFIED_TIME => new Zend_Db_Expr('NOW()') - ) + ] ); } } catch (Exception $e) { @@ -266,7 +266,7 @@ class PreferencesStore * * @throws NotWritableError In case the database operation failed */ - protected function update(array $preferences, $section) + protected function update(array $preferences, string $section): void { /** @var \Zend_Db_Adapter_Abstract $db */ $db = $this->getStoreConfig()->connection->getDbAdapter(); @@ -275,15 +275,15 @@ class PreferencesStore foreach ($preferences as $key => $value) { $db->update( $this->table, - array( + [ self::COLUMN_VALUE => $value, self::COLUMN_MODIFIED_TIME => new Zend_Db_Expr('NOW()') - ), - array( + ], + [ self::COLUMN_USERNAME . '=?' => $this->getUser()->getUsername(), $db->quoteIdentifier(self::COLUMN_SECTION) . '=?' => $section, $db->quoteIdentifier(self::COLUMN_PREFERENCE) . '=?' => $key - ) + ] ); } } catch (Exception $e) { @@ -303,7 +303,7 @@ class PreferencesStore * * @throws NotWritableError In case the database operation failed */ - protected function delete(array $preferenceKeys, $section) + protected function delete(array $preferenceKeys, string $section): void { /** @var \Zend_Db_Adapter_Abstract $db */ $db = $this->getStoreConfig()->connection->getDbAdapter(); @@ -311,11 +311,11 @@ class PreferencesStore try { $db->delete( $this->table, - array( + [ self::COLUMN_USERNAME . '=?' => $this->getUser()->getUsername(), $db->quoteIdentifier(self::COLUMN_SECTION) . '=?' => $section, $db->quoteIdentifier(self::COLUMN_PREFERENCE) . ' IN (?)' => $preferenceKeys - ) + ] ); } catch (Exception $e) { throw new NotWritableError( @@ -336,7 +336,7 @@ class PreferencesStore * * @throws ConfigurationError When the configuration defines an invalid storage type */ - public static function create(ConfigObject $config, User $user) + public static function create(ConfigObject $config, User $user): self { $resourceConfig = ResourceFactory::getResourceConfig($config->resource); if ($resourceConfig->db === 'mysql') { From 0eb3b27e6802f611788eccc35d94d1431f1599f0 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Mon, 2 May 2022 16:07:20 +0200 Subject: [PATCH 08/16] PreferencesStore: Remove not required method `setTable()` --- library/Icinga/User/Preferences/PreferencesStore.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/library/Icinga/User/Preferences/PreferencesStore.php b/library/Icinga/User/Preferences/PreferencesStore.php index 0468b097c..a26a1de25 100644 --- a/library/Icinga/User/Preferences/PreferencesStore.php +++ b/library/Icinga/User/Preferences/PreferencesStore.php @@ -132,16 +132,6 @@ class PreferencesStore return $this->user; } - /** - * Set the table to use - * - * @param string $table The table name - */ - public function setTable(string $table) - { - $this->table = $table; - } - /** * Initialize the store */ From a20d5ad1f6fd8879a693730de7b85a62ce62dbc7 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Mon, 2 May 2022 16:08:16 +0200 Subject: [PATCH 09/16] Update `80-Upgrading.md` --- doc/80-Upgrading.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/80-Upgrading.md b/doc/80-Upgrading.md index a24d4ac4c..ca7490094 100644 --- a/doc/80-Upgrading.md +++ b/doc/80-Upgrading.md @@ -6,6 +6,8 @@ v2.6 to v2.8 requires to follow the instructions for v2.7 too. ## Upgrading to Icinga Web 2 2.11.x * The Vagrant file and all its assets have been removed. +* The `IniStore` class has been removed due to the deprecation of the Preferences ini backend. +* The `DbStore` class has been removed and its methods have been added to `PreferencesStore` class. **Database Schema** From e7c368b09dc398f334b8c1ff0a042c2ff28d552a Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Tue, 3 May 2022 09:07:22 +0200 Subject: [PATCH 10/16] PreferencesStore: Fix syntax --- library/Icinga/User/Preferences/PreferencesStore.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/Icinga/User/Preferences/PreferencesStore.php b/library/Icinga/User/Preferences/PreferencesStore.php index a26a1de25..8ecc677c9 100644 --- a/library/Icinga/User/Preferences/PreferencesStore.php +++ b/library/Icinga/User/Preferences/PreferencesStore.php @@ -83,7 +83,7 @@ class PreferencesStore * * @var array */ - protected $preferences = array(); + protected $preferences = []; /** * Store config @@ -168,6 +168,7 @@ class PreferencesStore foreach ($result as $row) { $values[$row->{self::COLUMN_SECTION}][$row->{self::COLUMN_PREFERENCE}] = $row->{self::COLUMN_VALUE}; } + $this->preferences = $values; } @@ -189,9 +190,11 @@ class PreferencesStore if (! array_key_exists($section, $this->preferences)) { $this->preferences[$section] = []; } + if (! array_key_exists($section, $preferences)) { $preferences[$section] = []; } + $toBeInserted = array_diff_key($preferences[$section], $this->preferences[$section]); if (!empty($toBeInserted)) { $this->insert($toBeInserted, $section); @@ -201,6 +204,7 @@ class PreferencesStore array_diff_assoc($preferences[$section], $this->preferences[$section]), array_diff_assoc($this->preferences[$section], $preferences[$section]) ); + if (!empty($toBeUpdated)) { $this->update($toBeUpdated, $section); } From aad24195458a5aba91c3d8e90ba361fafb2a1457 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Tue, 3 May 2022 10:16:27 +0200 Subject: [PATCH 11/16] Remove obsolete `config_backend` option and not required code The user preferences backend is now always a `db`. --- application/controllers/AccountController.php | 2 +- .../forms/Config/GeneralConfigForm.php | 9 -- library/Icinga/Authentication/Auth.php | 32 +++---- .../Migrate/Config/UserDomainMigration.php | 95 +++++++------------ .../library/Setup/Steps/GeneralConfigStep.php | 11 +-- 5 files changed, 50 insertions(+), 99 deletions(-) diff --git a/application/controllers/AccountController.php b/application/controllers/AccountController.php index 9852e1111..f172cfeca 100644 --- a/application/controllers/AccountController.php +++ b/application/controllers/AccountController.php @@ -69,7 +69,7 @@ class AccountController extends Controller $form = new PreferenceForm(); $form->setPreferences($user->getPreferences()); - if ($config->get('config_backend', 'db') !== 'none' && isset($config->config_resource)) { + if (isset($config->config_resource)) { $form->setStore(PreferencesStore::create(new ConfigObject(array( 'resource' => $config->config_resource )), $user)); diff --git a/application/forms/Config/GeneralConfigForm.php b/application/forms/Config/GeneralConfigForm.php index 18d379094..5f15512a5 100644 --- a/application/forms/Config/GeneralConfigForm.php +++ b/application/forms/Config/GeneralConfigForm.php @@ -37,13 +37,4 @@ class GeneralConfigForm extends ConfigForm $this->addSubForm($themingConfigForm->create($formData)); $this->addSubForm($domainConfigForm->create($formData)); } - - public function onRequest() - { - parent::onRequest(); - - if ($this->config->get('global', 'config_backend') === 'ini') { - $this->warning('The preferences backend of type INI is deprecated and will be removed with version 2.11'); - } - } } diff --git a/library/Icinga/Authentication/Auth.php b/library/Icinga/Authentication/Auth.php index 45d815bd9..f358eac37 100644 --- a/library/Icinga/Authentication/Auth.php +++ b/library/Icinga/Authentication/Auth.php @@ -376,25 +376,21 @@ class Auth $config = new Config(); } - if ($config->get('global', 'config_backend', 'db') !== 'none') { - $preferencesConfig = new ConfigObject([ - 'resource' => $config->get('global', 'config_resource') - ]); + $preferencesConfig = new ConfigObject([ + 'resource' => $config->get('global', 'config_resource') + ]); - try { - $preferencesStore = PreferencesStore::create($preferencesConfig, $user); - $preferences = new Preferences($preferencesStore->load()); - } catch (Exception $e) { - Logger::error( - new IcingaException( - 'Cannot load preferences for user "%s". An exception was thrown: %s', - $user->getUsername(), - $e - ) - ); - $preferences = new Preferences(); - } - } else { + try { + $preferencesStore = PreferencesStore::create($preferencesConfig, $user); + $preferences = new Preferences($preferencesStore->load()); + } catch (Exception $e) { + Logger::error( + new IcingaException( + 'Cannot load preferences for user "%s". An exception was thrown: %s', + $user->getUsername(), + $e + ) + ); $preferences = new Preferences(); } diff --git a/modules/migrate/library/Migrate/Config/UserDomainMigration.php b/modules/migrate/library/Migrate/Config/UserDomainMigration.php index 1c3f6e181..855a0abdb 100644 --- a/modules/migrate/library/Migrate/Config/UserDomainMigration.php +++ b/modules/migrate/library/Migrate/Config/UserDomainMigration.php @@ -178,80 +178,51 @@ class UserDomainMigration { $config = Config::app(); - $type = $config->get('global', 'config_backend', 'ini'); + $resourceConfig = ResourceFactory::getResourceConfig($config->get('global', 'config_resource')); + if ($resourceConfig->db === 'mysql') { + $resourceConfig->charset = 'utf8mb4'; + } - switch ($type) { - case 'ini': - $directory = Config::resolvePath('preferences'); + /** @var DbConnection $conn */ + $conn = ResourceFactory::createResource($resourceConfig); - $migration = array(); + $query = $conn + ->select() + ->from('icingaweb_user_preference', array('username')) + ->group('username'); - if (DirectoryIterator::isReadable($directory)) { - foreach (new DirectoryIterator($directory) as $username => $path) { - $user = new User($username); + if ($this->map !== null) { + $query->applyFilter(Filter::matchAny(Filter::where('username', array_keys($this->map)))); + } - if (! $this->mustMigrate($user)) { - continue; - } + $users = $query->fetchColumn(); - $migrated = $this->migrateUser($user); + $migration = array(); - $migration[$path] = dirname($path) . '/' . $migrated->getUsername(); - } + foreach ($users as $username) { + $user = new User($username); - foreach ($migration as $from => $to) { - rename($from, $to); - } - } + if (! $this->mustMigrate($user)) { + continue; + } - break; - case 'db': - $resourceConfig = ResourceFactory::getResourceConfig($config->get('global', 'config_resource')); - if ($resourceConfig->db === 'mysql') { - $resourceConfig->charset = 'utf8mb4'; - } + $migrated = $this->migrateUser($user); - /** @var DbConnection $conn */ - $conn = ResourceFactory::createResource($resourceConfig); + $migration[$username] = $migrated->getUsername(); + } - $query = $conn - ->select() - ->from('icingaweb_user_preference', array('username')) - ->group('username'); + if (! empty($migration)) { + $conn->getDbAdapter()->beginTransaction(); - if ($this->map !== null) { - $query->applyFilter(Filter::matchAny(Filter::where('username', array_keys($this->map)))); - } + foreach ($migration as $originalUsername => $username) { + $conn->update( + 'icingaweb_user_preference', + array('username' => $username), + Filter::where('username', $originalUsername) + ); + } - $users = $query->fetchColumn(); - - $migration = array(); - - foreach ($users as $username) { - $user = new User($username); - - if (! $this->mustMigrate($user)) { - continue; - } - - $migrated = $this->migrateUser($user); - - $migration[$username] = $migrated->getUsername(); - } - - if (! empty($migration)) { - $conn->getDbAdapter()->beginTransaction(); - - foreach ($migration as $originalUsername => $username) { - $conn->update( - 'icingaweb_user_preference', - array('username' => $username), - Filter::where('username', $originalUsername) - ); - } - - $conn->getDbAdapter()->commit(); - } + $conn->getDbAdapter()->commit(); } } diff --git a/modules/setup/library/Setup/Steps/GeneralConfigStep.php b/modules/setup/library/Setup/Steps/GeneralConfigStep.php index f15ba6e52..2c928f63b 100644 --- a/modules/setup/library/Setup/Steps/GeneralConfigStep.php +++ b/modules/setup/library/Setup/Steps/GeneralConfigStep.php @@ -28,9 +28,7 @@ class GeneralConfigStep extends Step $config[$section][$property] = $value; } - if ($config['global']['config_backend'] === 'db') { - $config['global']['config_resource'] = $this->data['resourceName']; - } + $config['global']['config_resource'] = $this->data['resourceName']; try { Config::fromArray($config) @@ -57,12 +55,7 @@ class GeneralConfigStep extends Step ? t('An exception\'s stacktrace is shown to every user by default.') : t('An exception\'s stacktrace is hidden from every user by default.') ) . '' - . '
  • ' . sprintf( - $this->data['generalConfig']['global_config_backend'] === 'ini' ? sprintf( - t('Preferences will be stored per user account in INI files at: %s'), - Config::resolvePath('preferences') - ) : t('Preferences will be stored using a database.') - ) . '
  • ' + . '
  • ' . t('Preferences will be stored using a database.') . '
  • ' . ''; $type = $this->data['generalConfig']['logging_log']; From a250202fa35bdfe54ea362f4ca361db75cacf57a Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Tue, 3 May 2022 11:30:31 +0200 Subject: [PATCH 12/16] ApplicationConfigForm: Remove not required Form elements --- .../Config/General/ApplicationConfigForm.php | 66 +++++-------------- 1 file changed, 17 insertions(+), 49 deletions(-) diff --git a/application/forms/Config/General/ApplicationConfigForm.php b/application/forms/Config/General/ApplicationConfigForm.php index 63a32fa9b..fa1e3b82e 100644 --- a/application/forms/Config/General/ApplicationConfigForm.php +++ b/application/forms/Config/General/ApplicationConfigForm.php @@ -70,57 +70,25 @@ class ApplicationConfigForm extends Form ) ); - // we do not need this form for setup because we set the database there as default. - // this form is only displayed in configuration -> application if preferences backend type of ini is recognized - if (isset($formData['global_config_backend']) && $formData['global_config_backend'] === 'ini') { - $this->addElement( - 'select', - 'global_config_backend', - [ - 'required' => true, - 'autosubmit' => true, - 'label' => $this->translate('User Preference Storage Type'), - 'multiOptions' => [ - 'ini' => $this->translate('File System (INI Files)'), - 'db' => $this->translate('Database') - ] - ] - ); - } else { - $this->addElement( - 'hidden', - 'global_config_backend', - [ - 'required' => true, - 'value' => 'db', - 'disabled' => true - ] - ); + $backends = array(); + foreach (ResourceFactory::getResourceConfigs()->toArray() as $name => $resource) { + $backends[$name] = $name; } - if (! isset($formData['global_config_backend']) || $formData['global_config_backend'] === 'db') { - $backends = array(); - foreach (ResourceFactory::getResourceConfigs()->toArray() as $name => $resource) { - if ($resource['type'] === 'db') { - $backends[$name] = $name; - } - } - - $this->addElement( - 'select', - 'global_config_resource', - array( - 'required' => true, - 'multiOptions' => array_merge( - ['' => sprintf(' - %s - ', $this->translate('Please choose'))], - $backends - ), - 'disable' => [''], - 'value' => '', - 'label' => $this->translate('Configuration Database') - ) - ); - } + $this->addElement( + 'select', + 'global_config_resource', + array( + 'required' => true, + 'multiOptions' => array_merge( + ['' => sprintf(' - %s - ', $this->translate('Please choose'))], + $backends + ), + 'disable' => [''], + 'value' => '', + 'label' => $this->translate('Configuration Database') + ) + ); return $this; } From 41a23c3eb33c3bfbf86481cd6af2f8df1d6ade08 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Tue, 3 May 2022 13:34:07 +0200 Subject: [PATCH 13/16] Update doc --- doc/03-Configuration.md | 4 +--- doc/07-Preferences.md | 27 ++------------------------- 2 files changed, 3 insertions(+), 28 deletions(-) diff --git a/doc/03-Configuration.md b/doc/03-Configuration.md index 56f736030..a85416585 100644 --- a/doc/03-Configuration.md +++ b/doc/03-Configuration.md @@ -28,8 +28,7 @@ Option | Description -------------------------|----------------------------------------------- show\_stacktraces | **Optional.** Whether to show debug stacktraces. Defaults to `0`. module\_path | **Optional.** Specifies the directories where modules can be installed. Multiple directories must be separated with colons. -config\_backend | **Optional.** Select the user preference storage. Can be set to `ini` (default), `db` or `none`. If `db` is selected, this requires the `config_resource` attribute. -config\_resource | **Optional.** Specify a defined [resource](04-Resources.md#resources-configuration-database) name. Can only be used if `config_backend` is set to `db`. +config\_resource | **Required.** Specify a defined [resource](04-Resources.md#resources-configuration-database) name. Example for storing the user preferences in the database resource `icingaweb_db`: @@ -37,7 +36,6 @@ Example for storing the user preferences in the database resource `icingaweb_db` ``` [global] show_stacktraces = "0" -config_backend = "db" config_resource = "icingaweb_db" module_path = "/usr/share/icingaweb2/modules" ``` diff --git a/doc/07-Preferences.md b/doc/07-Preferences.md index 341e41ffb..73abead35 100644 --- a/doc/07-Preferences.md +++ b/doc/07-Preferences.md @@ -3,35 +3,13 @@ Preferences are settings a user can set for their account only, for example the language and time zone. -Preferences can be stored either in INI files or in a MySQL or in a PostgreSQL database. By default, Icinga Web 2 stores -preferences in INI files beneath Icinga Web 2's configuration directory. - -``` -/etc/icingaweb2//config.ini -``` +Preferences can be stored either in a MySQL or in a PostgreSQL database. The database must be configured. ## Configuration The preference configuration backend is defined in the global [config.ini](03-Configuration.md#configuration-general-global) file. -### Store Preferences in INI Files - -If preferences are stored in INI Files, Icinga Web 2 automatically creates one file per user using the username as -file name for storing preferences. A INI file is created once a user saves changed preferences the first time. -The files are located beneath the `preferences` directory beneath Icinga Web 2's configuration directory. - -You need to add the following section to the global [config.ini](03-Configuration.md#configuration-general-global) file -in order to store preferences in a file. - -``` -[global] -config_backend = "ini" -``` - -### Store Preferences in a Database - -In order to be more flexible in distributed setups you can store preferences in a MySQL or in a PostgreSQL database. -For storing preferences in a database, you have to define a [database resource](04-Resources.md#resources-configuration-database) +You have to define a [database resource](04-Resources.md#resources-configuration-database) which will be referenced as resource for the preferences storage. You need to add the following section to the global [config.ini](03-Configuration.md#configuration-general-global) file @@ -39,6 +17,5 @@ in order to store preferences in a database. ``` [global] -config_backend = "db" config_resource = "icingaweb_db" ``` From 09d378ab652f66562e7015994c0f8ea8d906519f Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Tue, 3 May 2022 14:31:02 +0200 Subject: [PATCH 14/16] ApplicationConfigForm: Remove loop and simplify the code --- application/forms/Config/General/ApplicationConfigForm.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/application/forms/Config/General/ApplicationConfigForm.php b/application/forms/Config/General/ApplicationConfigForm.php index fa1e3b82e..0e5c70028 100644 --- a/application/forms/Config/General/ApplicationConfigForm.php +++ b/application/forms/Config/General/ApplicationConfigForm.php @@ -70,10 +70,8 @@ class ApplicationConfigForm extends Form ) ); - $backends = array(); - foreach (ResourceFactory::getResourceConfigs()->toArray() as $name => $resource) { - $backends[$name] = $name; - } + $backends = array_keys(ResourceFactory::getResourceConfigs()->toArray()); + $backends = array_combine($backends, $backends); $this->addElement( 'select', From b3998856afd83f6bd8bab3f215ffc0c2fd38a358 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Tue, 3 May 2022 15:11:24 +0200 Subject: [PATCH 15/16] Revert "Remove class `PreferencesCommand`" This reverts commit ea03ecd779ffd8b38cbddefadf8c29712e4171dd. --- .../clicommands/PreferencesCommand.php | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 modules/migrate/application/clicommands/PreferencesCommand.php diff --git a/modules/migrate/application/clicommands/PreferencesCommand.php b/modules/migrate/application/clicommands/PreferencesCommand.php new file mode 100644 index 000000000..f31665493 --- /dev/null +++ b/modules/migrate/application/clicommands/PreferencesCommand.php @@ -0,0 +1,105 @@ + The resource to use, if no current database config backend is configured. + * --no-set-config-backend Do not set the given resource as config backend automatically + */ + public function indexAction() + { + $resource = Config::app()->get('global', 'config_resource'); + if (empty($resource)) { + $resource = $this->params->getRequired('resource'); + } + + $resourceConfig = ResourceFactory::getResourceConfig($resource); + if ($resourceConfig->db === 'mysql') { + $resourceConfig->charset = 'utf8mb4'; + } + + $connection = ResourceFactory::createResource($resourceConfig); + + $preferencesPath = Config::resolvePath('preferences'); + if (! file_exists($preferencesPath)) { + Logger::info('There are no local user preferences to migrate'); + return; + } + + $rc = 0; + + $preferenceDirs = new DirectoryIterator($preferencesPath); + foreach ($preferenceDirs as $preferenceDir) { + if (! is_dir($preferenceDir)) { + continue; + } + + $userName = basename($preferenceDir); + + Logger::info('Migrating INI preferences for user "%s" to database...', $userName); + + $iniStore = new IniStore(new ConfigObject(['location' => $preferencesPath]), new User($userName)); + $dbStore = new DbStore(new ConfigObject(['connection' => $connection]), new User($userName)); + + try { + $dbStore->load(); + $dbStore->save(new User\Preferences($iniStore->load())); + } catch (NotReadableError $e) { + if ($e->getPrevious() !== null) { + Logger::error('%s: %s', $e->getMessage(), $e->getPrevious()->getMessage()); + } else { + Logger::error($e->getMessage()); + } + + $rc = 128; + } catch (NotWritableError $e) { + Logger::error('%s: %s', $e->getMessage(), $e->getPrevious()->getMessage()); + $rc = 256; + } + } + + if ($rc > 0) { + Logger::error('Failed to migrate some user preferences'); + exit($rc); + } + + if ($this->params->has('resource') && ! $this->params->has('no-set-config-backend')) { + $appConfig = Config::app(); + $globalConfig = $appConfig->getSection('global'); + $globalConfig['config_backend'] = 'db'; + $globalConfig['config_resource'] = $resource; + + try { + $appConfig->saveIni(); + } catch (NotWritableError $e) { + Logger::error('Failed to update general configuration: %s', $e->getMessage()); + exit(256); + } + } + + Logger::info('Successfully migrated all local user preferences to database'); + } +} From bbbe9eef22e325c58d27e869569ad172fe019b94 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Tue, 3 May 2022 15:39:36 +0200 Subject: [PATCH 16/16] PreferencesCommand: Introduce method `loadIniFile()` and remove not required code --- .../clicommands/PreferencesCommand.php | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/modules/migrate/application/clicommands/PreferencesCommand.php b/modules/migrate/application/clicommands/PreferencesCommand.php index f31665493..11d1edb94 100644 --- a/modules/migrate/application/clicommands/PreferencesCommand.php +++ b/modules/migrate/application/clicommands/PreferencesCommand.php @@ -10,9 +10,9 @@ use Icinga\Data\ConfigObject; use Icinga\Data\ResourceFactory; use Icinga\Exception\NotReadableError; use Icinga\Exception\NotWritableError; +use Icinga\File\Ini\IniParser; use Icinga\User; -use Icinga\User\Preferences\Store\IniStore; -use Icinga\User\Preferences\Store\DbStore; +use Icinga\User\Preferences\PreferencesStore; use Icinga\Util\DirectoryIterator; class PreferencesCommand extends Command @@ -61,12 +61,15 @@ class PreferencesCommand extends Command Logger::info('Migrating INI preferences for user "%s" to database...', $userName); - $iniStore = new IniStore(new ConfigObject(['location' => $preferencesPath]), new User($userName)); - $dbStore = new DbStore(new ConfigObject(['connection' => $connection]), new User($userName)); + $dbStore = new PreferencesStore(new ConfigObject(['connection' => $connection]), new User($userName)); try { $dbStore->load(); - $dbStore->save(new User\Preferences($iniStore->load())); + $dbStore->save( + new User\Preferences( + $this->loadIniFile($preferencesPath, (new User($userName))->getUsername()) + ) + ); } catch (NotReadableError $e) { if ($e->getPrevious() !== null) { Logger::error('%s: %s', $e->getMessage(), $e->getPrevious()->getMessage()); @@ -89,7 +92,6 @@ class PreferencesCommand extends Command if ($this->params->has('resource') && ! $this->params->has('no-set-config-backend')) { $appConfig = Config::app(); $globalConfig = $appConfig->getSection('global'); - $globalConfig['config_backend'] = 'db'; $globalConfig['config_resource'] = $resource; try { @@ -102,4 +104,28 @@ class PreferencesCommand extends Command Logger::info('Successfully migrated all local user preferences to database'); } + + private function loadIniFile(string $filePath, string $username): array + { + $preferences = []; + $preferencesFile = sprintf( + '%s/%s/config.ini', + $filePath, + strtolower($username) + ); + + if (file_exists($preferencesFile)) { + if (! is_readable($preferencesFile)) { + throw new NotReadableError( + 'Preferences INI file %s for user %s is not readable', + $preferencesFile, + $username + ); + } else { + $preferences = IniParser::parseIniFile($preferencesFile)->toArray(); + } + } + + return $preferences; + } }