parent
f3ed73175b
commit
6112189b0c
|
@ -18,5 +18,15 @@ debug.enable = 1
|
|||
debug.type = stream
|
||||
debug.target = /tmp/icinga2.debug.log
|
||||
|
||||
; Use ini store to store preferences on local disk
|
||||
[preferences]
|
||||
type=ini
|
||||
|
||||
; Use database to store preference into mysql or postgres
|
||||
;[preferences]
|
||||
;type=db
|
||||
;dbtype=pgsql
|
||||
;dbhost=127.0.0.1
|
||||
;dbpassword=icingaweb
|
||||
;dbuser=icingaweb
|
||||
;dbname=icingaweb
|
|
@ -0,0 +1,7 @@
|
|||
create table `preferences`(
|
||||
`username` VARCHAR(255) NOT NULL,
|
||||
`preference` VARCHAR(100) NOT NULL,
|
||||
`value` VARCHAR(255) NOT NULL,
|
||||
|
||||
PRIMARY KEY(username, preference)
|
||||
);
|
|
@ -0,0 +1,7 @@
|
|||
create table "preferences"(
|
||||
"username" VARCHAR(255) NOT NULL,
|
||||
"preference" VARCHAR(100) NOT NULL,
|
||||
"value" VARCHAR(255) NOT NULL,
|
||||
|
||||
PRIMARY KEY(username, preference)
|
||||
);
|
|
@ -29,6 +29,7 @@
|
|||
namespace Icinga\Application;
|
||||
|
||||
use Icinga\Authentication\Manager as AuthenticationManager;
|
||||
use Icinga\Exception\ConfigurationError;
|
||||
use Icinga\User\Preferences;
|
||||
use Icinga\User;
|
||||
use Icinga\Web\Request;
|
||||
|
@ -40,7 +41,7 @@ use Zend_Controller_Action_HelperBroker;
|
|||
use Zend_Controller_Router_Route;
|
||||
use Zend_Controller_Action_Helper_ViewRenderer;
|
||||
use Icinga\Web\View;
|
||||
use Icinga\User\Preferences\StorageFactory;
|
||||
use Icinga\User\Preferences\StoreFactory;
|
||||
use Icinga\User\Preferences\SessionStore;
|
||||
|
||||
/**
|
||||
|
@ -201,6 +202,12 @@ class Web extends ApplicationBootstrap
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create user object and inject preference interface
|
||||
*
|
||||
* @throws ConfigurationError
|
||||
* @return User
|
||||
*/
|
||||
private function setupUser()
|
||||
{
|
||||
$authenticationManager = AuthenticationManager::getInstance(
|
||||
|
@ -211,17 +218,24 @@ class Web extends ApplicationBootstrap
|
|||
);
|
||||
|
||||
if ($authenticationManager->isAuthenticated() === true) {
|
||||
if ($this->getConfig()->preferences === null) {
|
||||
throw new ConfigurationError('Preferences not configured in config.ini');
|
||||
}
|
||||
|
||||
$user = $authenticationManager->getUser();
|
||||
|
||||
$this->getConfig()->preferences->configPath = $this->getConfigDir('preferences');
|
||||
|
||||
$preferenceStore = StorageFactory::create(
|
||||
$preferenceStore = StoreFactory::create(
|
||||
$this->getConfig()->preferences,
|
||||
$user
|
||||
);
|
||||
|
||||
// Needed to update values in user session
|
||||
$sessionStore = new SessionStore($authenticationManager->getSession());
|
||||
|
||||
// Performance: Do not ask provider if we've preferences
|
||||
// stored in session
|
||||
$initialPreferences = (count($sessionStore->load()))
|
||||
? $sessionStore->load() : $preferenceStore->load();
|
||||
|
||||
|
@ -232,6 +246,7 @@ class Web extends ApplicationBootstrap
|
|||
|
||||
$user->setPreferences($preferences);
|
||||
|
||||
// TESTING
|
||||
$requestCounter = $user->getPreferences()->get('test.request.counter', 0);
|
||||
$requestCounter++;
|
||||
$user->getPreferences()->set('test.request.counter', $requestCounter);
|
||||
|
|
|
@ -34,21 +34,21 @@ namespace Icinga\User\Preferences;
|
|||
class ChangeSet
|
||||
{
|
||||
/**
|
||||
* Items to update
|
||||
* Stack of pending updates
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $update = array();
|
||||
|
||||
/**
|
||||
* Items to delete
|
||||
* Stack of pending delete operations
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $delete = array();
|
||||
|
||||
/**
|
||||
* Items to create
|
||||
* Stack of pending create operations
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
|
@ -56,6 +56,7 @@ class ChangeSet
|
|||
|
||||
/**
|
||||
* Push an update to stack
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
|
@ -66,6 +67,7 @@ class ChangeSet
|
|||
|
||||
/**
|
||||
* Getter for pending updates
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getUpdate()
|
||||
|
@ -84,6 +86,8 @@ class ChangeSet
|
|||
}
|
||||
|
||||
/**
|
||||
* Get pending delete operations
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDelete()
|
||||
|
@ -95,13 +99,18 @@ class ChangeSet
|
|||
* Push create operation to stack
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function appendCreate($key, $value)
|
||||
{
|
||||
$this->create[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pending create operations
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getCreate()
|
||||
{
|
||||
return $this->create;
|
||||
|
|
|
@ -0,0 +1,241 @@
|
|||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\User\Preferences;
|
||||
|
||||
use Icinga\User;
|
||||
use SplSubject;
|
||||
use Zend_Db_Adapter_Abstract;
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
use Icinga\User\Preferences;
|
||||
|
||||
/**
|
||||
* Store user preferences in database
|
||||
*/
|
||||
class DbStore implements LoadInterface, FlushObserverInterface
|
||||
{
|
||||
/**
|
||||
* Column name for username
|
||||
*/
|
||||
const COLUMN_USERNAME = 'username';
|
||||
|
||||
/**
|
||||
* Column name for preference
|
||||
*/
|
||||
const COLUMN_PREFERENCE = 'preference';
|
||||
|
||||
/**
|
||||
* Column name for value
|
||||
*/
|
||||
const COLUMN_VALUE = 'value';
|
||||
|
||||
/**
|
||||
* User object
|
||||
*
|
||||
* @var User
|
||||
*/
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* Zend database adapter
|
||||
*
|
||||
* @var Zend_Db_Adapter_Abstract
|
||||
*/
|
||||
private $dbAdapter;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $table = 'preferences';
|
||||
|
||||
/**
|
||||
* Setter for user
|
||||
*
|
||||
* @param User $user
|
||||
*/
|
||||
public function setUser(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for db adapter
|
||||
*
|
||||
* @param Zend_Db_Adapter_Abstract $dbAdapter
|
||||
*/
|
||||
public function setDbAdapter(Zend_Db_Adapter_Abstract $dbAdapter)
|
||||
{
|
||||
$this->dbAdapter = $dbAdapter;
|
||||
$this->dbAdapter->getProfiler()->setEnabled(true);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for table
|
||||
*
|
||||
* @param string $table
|
||||
*/
|
||||
public function setTable($table)
|
||||
{
|
||||
$this->table = $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load preferences from source
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function load()
|
||||
{
|
||||
$res = $this->dbAdapter->select()->from($this->table)
|
||||
->where('username=?', $this->user->getUsername())
|
||||
->query();
|
||||
|
||||
$out = array();
|
||||
|
||||
foreach ($res->fetchAll() as $row) {
|
||||
$out[$row[self::COLUMN_PREFERENCE]] = $row[self::COLUMN_VALUE];
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create zend db suitable where condition
|
||||
*
|
||||
* @param string $preference
|
||||
* @return array
|
||||
*/
|
||||
private function createWhereCondition($preference)
|
||||
{
|
||||
return array(
|
||||
self::COLUMN_USERNAME. '=?' => $this->user->getUsername(),
|
||||
self::COLUMN_PREFERENCE. '=?' => $preference
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create operation
|
||||
*
|
||||
* @param string $preference
|
||||
* @param mixed $value
|
||||
* @return int
|
||||
*/
|
||||
private function doCreate($preference, $value)
|
||||
{
|
||||
return $this->dbAdapter->insert(
|
||||
$this->table,
|
||||
array(
|
||||
self::COLUMN_USERNAME => $this->user->getUsername(),
|
||||
self::COLUMN_PREFERENCE => $preference,
|
||||
self::COLUMN_VALUE => $value
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update operation
|
||||
*
|
||||
* @param string $preference
|
||||
* @param mixed $value
|
||||
* @return int
|
||||
*/
|
||||
private function doUpdate($preference, $value)
|
||||
{
|
||||
return $this->dbAdapter->update(
|
||||
$this->table,
|
||||
array(
|
||||
self::COLUMN_VALUE => $value
|
||||
),
|
||||
$this->createWhereCondition($preference)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete preference operation
|
||||
*
|
||||
* @param string $preference
|
||||
* @return int
|
||||
*/
|
||||
private function doDelete($preference)
|
||||
{
|
||||
return $this->dbAdapter->delete(
|
||||
$this->table,
|
||||
$this->createWhereCondition($preference)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive update from subject
|
||||
*
|
||||
* @link http://php.net/manual/en/splobserver.update.php
|
||||
* @param SplSubject $subject
|
||||
* @throws ProgrammingError
|
||||
*/
|
||||
public function update(SplSubject $subject)
|
||||
{
|
||||
if (!$subject instanceof Preferences) {
|
||||
throw new ProgrammingError('Not compatible with '. get_class($subject));
|
||||
}
|
||||
|
||||
$changeSet = $subject->getChangeSet();
|
||||
|
||||
foreach ($changeSet->getCreate() as $key => $value) {
|
||||
$retVal = $this->doCreate($key, $value);
|
||||
|
||||
if (!$retVal) {
|
||||
throw new ProgrammingError('Could not create preference value in db: '. $key. '='. $value);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($changeSet->getUpdate() as $key => $value) {
|
||||
$retVal = $this->doUpdate($key, $value);
|
||||
|
||||
/*
|
||||
* Fallback if we switch storage type while user logged in
|
||||
*/
|
||||
if (!$retVal) {
|
||||
$retVal = $this->doCreate($key, $value);
|
||||
|
||||
if (!$retVal) {
|
||||
throw new ProgrammingError('Could not create preference value in db: '. $key. '='. $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($changeSet->getDelete() as $key) {
|
||||
$retVal = $this->doDelete($key);
|
||||
|
||||
if (!$retVal) {
|
||||
throw new ProgrammingError('Could not delete preference value in db: '. $key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,8 +31,12 @@ namespace Icinga\User\Preferences;
|
|||
use Icinga\User;
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
use \Zend_Config;
|
||||
use \Zend_Db;
|
||||
|
||||
final class StorageFactory
|
||||
/**
|
||||
* Create preference stores from zend config
|
||||
*/
|
||||
final class StoreFactory
|
||||
{
|
||||
/**
|
||||
* Prefix for classes containing namespace
|
||||
|
@ -66,6 +70,31 @@ final class StorageFactory
|
|||
$items = $config->toArray();
|
||||
unset($items['type']);
|
||||
|
||||
// TODO(mh): Encapsulate into a db adapter factory (#4503)
|
||||
if (isset($items['dbname'])
|
||||
&& isset($items['dbuser'])
|
||||
&& isset($items['dbpassword'])
|
||||
&& isset($items['dbhost'])
|
||||
&& isset($items['dbtype'])
|
||||
) {
|
||||
$zendDbType = 'PDO_'. strtoupper($items['dbtype']);
|
||||
|
||||
$zendDbOptions = array(
|
||||
'host' => $items['dbhost'],
|
||||
'username' => $items['dbuser'],
|
||||
'password' => $items['dbpassword'],
|
||||
'dbname' => $items['dbname']
|
||||
);
|
||||
|
||||
if (isset($items['port'])) {
|
||||
$zendDbOptions['port'] = $items['port'];
|
||||
}
|
||||
|
||||
$dbAdapter = Zend_Db::factory($zendDbType, $zendDbOptions);
|
||||
|
||||
$items['dbAdapter'] = $dbAdapter;
|
||||
}
|
||||
|
||||
foreach ($items as $key => $value) {
|
||||
$setter = 'set'. ucfirst($key);
|
||||
if (is_callable(array($store, $setter))) {
|
|
@ -0,0 +1,169 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Icinga\User\Preferences;
|
||||
|
||||
require_once __DIR__. '/../../../../../../library/Icinga/Exception/ConfigurationError.php';
|
||||
require_once __DIR__. '/../../../../../../library/Icinga/User.php';
|
||||
require_once __DIR__. '/../../../../../../library/Icinga/User/Preferences.php';
|
||||
require_once __DIR__. '/../../../../../../library/Icinga/User/Preferences/LoadInterface.php';
|
||||
require_once __DIR__. '/../../../../../../library/Icinga/User/Preferences/FlushObserverInterface.php';
|
||||
require_once __DIR__. '/../../../../../../library/Icinga/User/Preferences/DbStore.php';
|
||||
|
||||
require_once 'Zend/Db.php';
|
||||
require_once 'Zend/Db/Adapter/Abstract.php';
|
||||
|
||||
use Icinga\User;
|
||||
use Icinga\User\Preferences\DbStore;
|
||||
use Icinga\User\Preferences;
|
||||
use \PHPUnit_Framework_TestCase;
|
||||
use \Zend_Db;
|
||||
use \Zend_Db_Adapter_Abstract;
|
||||
use \PDOException;
|
||||
use \Exception;
|
||||
|
||||
class DbStoreTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
const TYPE_MYSQL = 'mysql';
|
||||
|
||||
const TYPE_PGSQL = 'pgsql';
|
||||
|
||||
private $database = 'icinga_unittest';
|
||||
|
||||
private $table = 'preferences';
|
||||
|
||||
private $databaseConfig = array(
|
||||
'host' => '127.0.0.1',
|
||||
'username' => 'icinga_unittest',
|
||||
'password' => 'icinga_unittest',
|
||||
'dbname' => 'icinga_unittest'
|
||||
);
|
||||
|
||||
/**
|
||||
* @var Zend_Db_Adapter_Abstract
|
||||
*/
|
||||
private $dbMysql;
|
||||
|
||||
/**
|
||||
* @var Zend_Db_Adapter_Abstract
|
||||
*/
|
||||
private $dbPgsql;
|
||||
|
||||
private function createDb($type)
|
||||
{
|
||||
$zendType = 'PDO_'. strtoupper($type);
|
||||
|
||||
if ($type === self::TYPE_MYSQL) {
|
||||
$this->databaseConfig['port'] = 3306;
|
||||
} elseif ($type === self::TYPE_PGSQL) {
|
||||
$this->databaseConfig['port'] = 5432;
|
||||
}
|
||||
|
||||
$db = Zend_Db::factory(
|
||||
$zendType,
|
||||
$this->databaseConfig
|
||||
);
|
||||
|
||||
try {
|
||||
$db->getConnection();
|
||||
|
||||
$dumpFile = realpath(__DIR__. '/../../../../../../etc/schema/preferences.'. strtolower($type). '.sql');
|
||||
|
||||
if (!$dumpFile) {
|
||||
throw new Exception('Dumpfile for db type not found: '. $type);
|
||||
}
|
||||
|
||||
try {
|
||||
$db->getConnection()->exec(file_get_contents($dumpFile));
|
||||
} catch (PDOException $e) {
|
||||
// PASS
|
||||
}
|
||||
|
||||
} catch (\Zend_Db_Adapter_Exception $e) {
|
||||
return null;
|
||||
} catch (PDOException $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $db;
|
||||
}
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->dbMysql = $this->createDb(self::TYPE_MYSQL);
|
||||
$this->dbPgsql = $this->createDb(self::TYPE_PGSQL);
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
if ($this->dbMysql) {
|
||||
$this->dbMysql->getConnection()->exec('DROP TABLE '. $this->table);
|
||||
}
|
||||
|
||||
if ($this->dbPgsql) {
|
||||
$this->dbPgsql->getConnection()->exec('DROP TABLE '. $this->table);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function createDbStore(Zend_Db_Adapter_Abstract $db)
|
||||
{
|
||||
$user = new User('jdoe');
|
||||
|
||||
$store = new DbStore();
|
||||
$store->setDbAdapter($db);
|
||||
$store->setUser($user);
|
||||
|
||||
return $store;
|
||||
}
|
||||
|
||||
public function testCreateUpdateDeletePreferenceValuesMySQL()
|
||||
{
|
||||
if ($this->dbMysql) {
|
||||
$store = $this->createDbStore($this->dbMysql);
|
||||
|
||||
$preferences = new Preferences(array());
|
||||
$preferences->attach($store);
|
||||
|
||||
$preferences->set('test.key1', 'OK1');
|
||||
$preferences->set('test.key2', 'OK2');
|
||||
$preferences->set('test.key3', 'OK2');
|
||||
|
||||
$preferences->remove('test.key2');
|
||||
|
||||
$preferences->set('test.key3', 'OKOK333');
|
||||
|
||||
$preferencesTest = new Preferences($store->load());
|
||||
$this->assertEquals('OK1', $preferencesTest->get('test.key1'));
|
||||
$this->assertNull($preferencesTest->get('test.key2'));
|
||||
$this->assertEquals('OKOK333', $preferencesTest->get('test.key3'));
|
||||
} else {
|
||||
$this->markTestSkipped('MySQL test environment is not configured');
|
||||
}
|
||||
}
|
||||
|
||||
public function testCreateUpdateDeletePreferenceValuesPgSQL()
|
||||
{
|
||||
if ($this->dbPgsql) {
|
||||
$store = $this->createDbStore($this->dbPgsql);
|
||||
|
||||
$preferences = new Preferences(array());
|
||||
$preferences->attach($store);
|
||||
|
||||
$preferences->set('test.key1', 'OK1');
|
||||
$preferences->set('test.key2', 'OK2');
|
||||
$preferences->set('test.key3', 'OK2');
|
||||
|
||||
$preferences->remove('test.key2');
|
||||
|
||||
$preferences->set('test.key3', 'OKOK333');
|
||||
|
||||
$preferencesTest = new Preferences($store->load());
|
||||
$this->assertEquals('OK1', $preferencesTest->get('test.key1'));
|
||||
$this->assertNull($preferencesTest->get('test.key2'));
|
||||
$this->assertEquals('OKOK333', $preferencesTest->get('test.key3'));
|
||||
} else {
|
||||
$this->markTestSkipped('PgSQL test environment is not configured');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue