From 98fbfe68a8562c442ac127067d9df341c4504313 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 10 Sep 2014 14:48:33 +0200 Subject: [PATCH 001/318] Redirect to /setup if necessary A user gets now redirected to /setup in case he is not logged in, the token file exists and no config.ini was found. refs #7163 --- .../controllers/AuthenticationController.php | 11 ++++++++ application/controllers/SetupController.php | 25 +++++++++++++++++++ application/views/scripts/setup/index.phtml | 0 3 files changed, 36 insertions(+) create mode 100644 application/controllers/SetupController.php create mode 100644 application/views/scripts/setup/index.phtml diff --git a/application/controllers/AuthenticationController.php b/application/controllers/AuthenticationController.php index b87779669..402ad3411 100644 --- a/application/controllers/AuthenticationController.php +++ b/application/controllers/AuthenticationController.php @@ -33,6 +33,17 @@ class AuthenticationController extends ActionController */ public function loginAction() { + if (@file_exists(Config::$configDir . '/setup.token')) { + try { + $config = Config::app()->toArray(); + if (empty($config)) { + $this->redirectNow(Url::fromPath('setup')); + } + } catch (NotReadableError $e) { + // Gets thrown in case of insufficient permission only + } + } + $auth = $this->Auth(); $this->view->form = $form = new LoginForm(); $this->view->title = $this->translate('Icingaweb Login'); diff --git a/application/controllers/SetupController.php b/application/controllers/SetupController.php new file mode 100644 index 000000000..fa8e3c68d --- /dev/null +++ b/application/controllers/SetupController.php @@ -0,0 +1,25 @@ + Date: Wed, 10 Sep 2014 14:49:38 +0200 Subject: [PATCH 002/318] Add Cli command to generate and show setup tokens One is now able to utilize `icingacli setup (show/generate)Token` refs #7163 --- application/clicommands/SetupCommand.php | 63 ++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 application/clicommands/SetupCommand.php diff --git a/application/clicommands/SetupCommand.php b/application/clicommands/SetupCommand.php new file mode 100644 index 000000000..f6b6aff12 --- /dev/null +++ b/application/clicommands/SetupCommand.php @@ -0,0 +1,63 @@ + [] + */ +class SetupCommand extends Command +{ + /** + * Display the current setup token + * + * Shows you the current setup token used to authenticate when installing Icinga Web 2 using the web-based wizard + * + * USAGE: + * + * icingacli setup showToken + */ + public function showTokenAction() + { + $token = @file_get_contents($this->app->getConfigDir() . '/setup.token'); + if (! $token) { + $this->fail( + $this->translate('Nothing to show. Please create a new setup token using the generateToken action.') + ); + } + + printf($this->translate("The current setup token is: %s\n"), $token); + } + + /** + * Create a new setup token + * + * Re-generates the setup token used to authenticate when installing Icinga Web 2 using the web-based wizard + * + * USAGE: + * + * icingacli setup generateToken + */ + public function generateTokenAction() + { + $token = bin2hex(openssl_random_pseudo_bytes(8)); + $filepath = $this->app->getConfigDir() . '/setup.token'; + + if (false === @file_put_contents($filepath, $token)) { + $this->fail($this->translate('Cannot write setup token to disk.')); + } + + if (false === @chmod($filepath, 0640)) { + $this->fail(sprintf($this->translate('Cannot change access mode of "%s" to %o.'), $filepath, 0640)); + } + + printf($this->translate("The newly generated setup token is: %s\n"), $token); + } +} From c7727879678639da30e06a4d4403eff6371e54a1 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 10 Sep 2014 14:52:34 +0200 Subject: [PATCH 003/318] Re-add TokenValidator refs #7163 --- .../Web/Form/Validator/TokenValidator.php | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 library/Icinga/Web/Form/Validator/TokenValidator.php diff --git a/library/Icinga/Web/Form/Validator/TokenValidator.php b/library/Icinga/Web/Form/Validator/TokenValidator.php new file mode 100644 index 000000000..eb07d5438 --- /dev/null +++ b/library/Icinga/Web/Form/Validator/TokenValidator.php @@ -0,0 +1,71 @@ +tokenPath = $tokenPath; + $this->_messageTemplates = array( + 'TOKEN_FILE_NOT_FOUND' => t('Cannot validate token, file could not be opened or does not exist.'), + 'TOKEN_FILE_EMPTY' => t('Cannot validate token, file is empty. Please define a token.'), + 'TOKEN_FILE_PUBLIC' => t('Cannot validate token, file is publicly readable.'), + 'TOKEN_INVALID' => t('Invalid token supplied.') + ); + } + + /** + * Validate the given token with the one in the token-file + * + * @param string $value The token to validate + * @param null $context The form context (ignored) + * + * @return bool + */ + public function isValid($value, $context = null) + { + $tokenStats = stat($this->tokenPath); + if (($tokenStats['mode'] & 4) === 4) { + $this->_error('TOKEN_FILE_PUBLIC'); + return false; + } + + $expectedToken = @file_get_contents($this->tokenPath); + if ($expectedToken === false) { + $this->_error('TOKEN_FILE_NOT_FOUND'); + return false; + } + + $expectedToken = trim($expectedToken); + if (empty($expectedToken)) { + $this->_error('TOKEN_FILE_EMPTY'); + return false; + } elseif ($value !== $expectedToken) { + $this->_error('TOKEN_INVALID'); + return false; + } + + return true; + } +} + From 80c42ce499318e5c6213ec5bbcb657c58e3d0d18 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 17 Sep 2014 09:43:10 +0200 Subject: [PATCH 004/318] Fix autosubmit of Icinga\Form\Config\General\LoggingConfigForm --- application/forms/Config/General/LoggingConfigForm.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/forms/Config/General/LoggingConfigForm.php b/application/forms/Config/General/LoggingConfigForm.php index 135dd9e28..3d07d4eb6 100644 --- a/application/forms/Config/General/LoggingConfigForm.php +++ b/application/forms/Config/General/LoggingConfigForm.php @@ -44,7 +44,7 @@ class LoggingConfigForm extends Form 'logging_type', array( 'required' => true, - 'class' => 'autosubmit', + 'autosubmit' => true, 'label' => t('Logging Type'), 'description' => t('The type of logging to utilize.'), 'multiOptions' => array( From efab6c7827ebf2fb0fd8df1302d15462cf417d1f Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 17 Sep 2014 09:45:07 +0200 Subject: [PATCH 005/318] Add method set/getByRef to Icinga\Web\Session\SessionNamespace --- .../Icinga/Web/Session/SessionNamespace.php | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/library/Icinga/Web/Session/SessionNamespace.php b/library/Icinga/Web/Session/SessionNamespace.php index b7495683b..fec3c5dca 100644 --- a/library/Icinga/Web/Session/SessionNamespace.php +++ b/library/Icinga/Web/Session/SessionNamespace.php @@ -126,6 +126,17 @@ class SessionNamespace implements IteratorAggregate return $this; } + public function setByRef($key, &$value) + { + $this->values[$key] = $value; + + if (in_array($key, $this->removed)) { + unset($this->removed[array_search($key, $this->removed)]); + } + + return $this; + } + /** * Getter for session values * @@ -139,6 +150,16 @@ class SessionNamespace implements IteratorAggregate return isset($this->values[$key]) ? $this->values[$key] : $default; } + public function & getByRef($key, $default = null) + { + $value = $default; + if (isset($this->values[$key])) { + $value = & $this->values[$key]; + } + + return $value; + } + /** * Delete the given value from the session * From 7fcd665aaad3b49d6b4abd471797f1e683bded8d Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 17 Sep 2014 09:45:47 +0200 Subject: [PATCH 006/318] Fix value invalidation in Icinga\Web\Session\SessionNamespace --- library/Icinga/Web/Session/SessionNamespace.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Web/Session/SessionNamespace.php b/library/Icinga/Web/Session/SessionNamespace.php index fec3c5dca..02a33dcd4 100644 --- a/library/Icinga/Web/Session/SessionNamespace.php +++ b/library/Icinga/Web/Session/SessionNamespace.php @@ -120,7 +120,7 @@ class SessionNamespace implements IteratorAggregate $this->values[$key] = $value; if (in_array($key, $this->removed)) { - unset($this->removed[array_search($key, $this->values)]); + unset($this->removed[array_search($key, $this->removed)]); } return $this; From c00dbf9f460d0c55fae2434f693a6b575f20c4f4 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 17 Sep 2014 10:42:56 +0200 Subject: [PATCH 007/318] Write session on response There should not be any necessity to write the session once changes are being made to it. We now track whether changes were made and write the session when responding to the user's request if so. --- application/forms/PreferenceForm.php | 5 +--- library/Icinga/Authentication/Manager.php | 5 +--- .../Web/Controller/ActionController.php | 29 ++++++++++++++----- library/Icinga/Web/Notification.php | 14 +++------ library/Icinga/Web/Response.php | 6 ++++ library/Icinga/Web/Session/Session.php | 13 +++++++-- .../Icinga/Web/Session/SessionNamespace.php | 19 ++++++++++++ .../Monitoring/Backend/Ido/Query/IdoQuery.php | 1 - .../library/Monitoring/Timeline/TimeLine.php | 1 - 9 files changed, 64 insertions(+), 29 deletions(-) diff --git a/application/forms/PreferenceForm.php b/application/forms/PreferenceForm.php index cdb6ff288..8eb445ab8 100644 --- a/application/forms/PreferenceForm.php +++ b/application/forms/PreferenceForm.php @@ -99,10 +99,7 @@ class PreferenceForm extends Form } $this->preferences->icingaweb = $webPreferences; - // TODO: Is this even necessary in case the session is written on response? - $session = Session::getSession(); - $session->user->setPreferences($this->preferences); - $session->write(); + Session::getSession()->user->setPreferences($this->preferences); try { $this->save(); diff --git a/library/Icinga/Authentication/Manager.php b/library/Icinga/Authentication/Manager.php index efe9b5815..6787fea3f 100644 --- a/library/Icinga/Authentication/Manager.php +++ b/library/Icinga/Authentication/Manager.php @@ -106,10 +106,7 @@ class Manager */ public function persistCurrentUser() { - $session = Session::getSession(); - $session->set('user', $this->user); - $session->write(); - $session->refreshId(); + Session::getSession()->set('user', $this->user)->refreshId(); } /** diff --git a/library/Icinga/Web/Controller/ActionController.php b/library/Icinga/Web/Controller/ActionController.php index d3fc54535..eade1c124 100644 --- a/library/Icinga/Web/Controller/ActionController.php +++ b/library/Icinga/Web/Controller/ActionController.php @@ -18,8 +18,6 @@ use Icinga\File\Pdf; use Icinga\Exception\ProgrammingError; use Icinga\Web\Session; use Icinga\Web\UrlParams; -use Icinga\Session\SessionNamespace; -use Icinga\Exception\NotReadableError; use Zend_Controller_Action; use Zend_Controller_Action_HelperBroker as ActionHelperBroker; use Zend_Controller_Request_Abstract as Request; @@ -325,14 +323,24 @@ class ActionController extends Zend_Controller_Action $this->getResponse()->setHeader('X-Icinga-Reload-Css', 'now'); } + $this->shutdownSession(); + $this->getResponse() ->setHeader('X-Icinga-Redirect', rawurlencode($url->getAbsoluteUrl())) ->sendHeaders(); - // TODO: Session shutdown? exit; } + protected function redirectHttp($url) + { + if (! $url instanceof Url) { + $url = Url::fromPath($url); + } + $this->shutdownSession(); + $this->_helper->Redirector->gotoUrlAndExit($url->getRelativeUrl()); + } + /** * Redirect to a specific url, updating the browsers URL field * @@ -343,10 +351,7 @@ class ActionController extends Zend_Controller_Action if ($this->isXhr()) { $this->redirectXhr($url); } else { - if (! $url instanceof Url) { - $url = Url::fromPath($url); - } - $this->_helper->Redirector->gotoUrlAndExit($url->getRelativeUrl()); + $this->redirectHttp($url); } } @@ -372,6 +377,8 @@ class ActionController extends Zend_Controller_Action } } + $this->shutdownSession(); + if ($req->getParam('format') === 'pdf') { $layout->setLayout('pdf'); $this->sendAsPdf(); @@ -430,6 +437,14 @@ class ActionController extends Zend_Controller_Action $pdf->renderControllerAction($this); } + protected function shutdownSession() + { + $session = Session::getSession(); + if ($session->hasChanged()) { + $session->write(); + } + } + /** * Render the benchmark * diff --git a/library/Icinga/Web/Notification.php b/library/Icinga/Web/Notification.php index 178a07e0a..b9cc839ab 100644 --- a/library/Icinga/Web/Notification.php +++ b/library/Icinga/Web/Notification.php @@ -80,30 +80,24 @@ class Notification 'message' => $message, ); - // Get, change, set - just to be on the safe side: $session = Session::getSession(); - $msgs = $session->messages; - $msgs[] = $mo; - $session->messages = $msgs; - $session->write(); + $session->messages[] = $mo; } public function hasMessages() { $session = Session::getSession(); - return !empty($session->messages); + return false === empty($session->messages); } public function getMessages() { $session = Session::getSession(); - $msgs = $session->messages; - if (false === empty($msgs)) { + if (false === empty($session->messages)) { $session->messages = array(); - $session->write(); } - return $msgs; + return $session->messages; } final private function __construct() diff --git a/library/Icinga/Web/Response.php b/library/Icinga/Web/Response.php index 6ad4f6f87..d0d8912e6 100644 --- a/library/Icinga/Web/Response.php +++ b/library/Icinga/Web/Response.php @@ -21,6 +21,12 @@ class Response extends Zend_Controller_Response_Http } else { $this->setRedirect($url->getAbsoluteUrl()); } + + $session = Session::getSession(); + if ($session->hasChanged()) { + $session->write(); + } + $this->sendHeaders(); exit; } diff --git a/library/Icinga/Web/Session/Session.php b/library/Icinga/Web/Session/Session.php index 0c60d7f98..0991ec708 100644 --- a/library/Icinga/Web/Session/Session.php +++ b/library/Icinga/Web/Session/Session.php @@ -104,13 +104,22 @@ abstract class Session extends SessionNamespace $this->removedNamespaces[] = $identifier; } + /** + * Return whether the session has changed + * + * @return bool + */ + public function hasChanged() + { + return parent::hasChanged() || false === empty($this->namespaces) || false === empty($this->removedNamespaces); + } + /** * Clear all values and namespaces from the session cache */ public function clear() { - $this->values = array(); - $this->removed = array(); + parent::clear(); $this->namespaces = array(); $this->removedNamespaces = array(); } diff --git a/library/Icinga/Web/Session/SessionNamespace.php b/library/Icinga/Web/Session/SessionNamespace.php index 02a33dcd4..76837adda 100644 --- a/library/Icinga/Web/Session/SessionNamespace.php +++ b/library/Icinga/Web/Session/SessionNamespace.php @@ -208,4 +208,23 @@ class SessionNamespace implements IteratorAggregate $this->session->write(); } + + /** + * Return whether the session namespace has been changed + * + * @return bool + */ + public function hasChanged() + { + return false === empty($this->values) || false === empty($this->removed); + } + + /** + * Clear all values from the session namespace + */ + public function clear() + { + $this->values = array(); + $this->removed = array(); + } } diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php index 06472b44d..566fac5e3 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php @@ -655,7 +655,6 @@ abstract class IdoQuery extends DbQuery ); if ($session !== null) { $session->version = self::$idoVersion; - $session->write(); // <- WHY? I don't want to care about this! } } return self::$idoVersion; diff --git a/modules/monitoring/library/Monitoring/Timeline/TimeLine.php b/modules/monitoring/library/Monitoring/Timeline/TimeLine.php index 3a795df32..93dc5a0ef 100644 --- a/modules/monitoring/library/Monitoring/Timeline/TimeLine.php +++ b/modules/monitoring/library/Monitoring/Timeline/TimeLine.php @@ -288,7 +288,6 @@ class TimeLine implements IteratorAggregate if ($this->session !== null) { $this->session->calculationBase = $new; - $this->session->write(); } } else { $this->calculationBase = $calculationBase; From 6f988cb94cbc26f676db93c7b663b5efe365f62a Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 17 Sep 2014 10:43:52 +0200 Subject: [PATCH 008/318] Remove method write() from Icinga\Web\Session\SessionNamespace --- library/Icinga/Web/Session/PhpSession.php | 2 +- library/Icinga/Web/Session/Session.php | 2 +- .../Icinga/Web/Session/SessionNamespace.php | 29 ------------------- .../Web/Session/SessionNamespaceTest.php | 23 --------------- 4 files changed, 2 insertions(+), 54 deletions(-) diff --git a/library/Icinga/Web/Session/PhpSession.php b/library/Icinga/Web/Session/PhpSession.php index 65f940a13..d0d44530c 100644 --- a/library/Icinga/Web/Session/PhpSession.php +++ b/library/Icinga/Web/Session/PhpSession.php @@ -121,7 +121,7 @@ class PhpSession extends Session foreach ($_SESSION as $key => $value) { if (strpos($key, self::NAMESPACE_PREFIX) === 0) { - $namespace = new SessionNamespace($this); + $namespace = new SessionNamespace(); $namespace->setAll($value); $this->namespaces[substr($key, strlen(self::NAMESPACE_PREFIX))] = $namespace; } else { diff --git a/library/Icinga/Web/Session/Session.php b/library/Icinga/Web/Session/Session.php index 0991ec708..ab73dcac7 100644 --- a/library/Icinga/Web/Session/Session.php +++ b/library/Icinga/Web/Session/Session.php @@ -75,7 +75,7 @@ abstract class Session extends SessionNamespace unset($this->removedNamespaces[array_search($identifier, $this->removedNamespaces)]); } - $this->namespaces[$identifier] = new SessionNamespace($this); + $this->namespaces[$identifier] = new SessionNamespace(); } return $this->namespaces[$identifier]; diff --git a/library/Icinga/Web/Session/SessionNamespace.php b/library/Icinga/Web/Session/SessionNamespace.php index 76837adda..bb67838d6 100644 --- a/library/Icinga/Web/Session/SessionNamespace.php +++ b/library/Icinga/Web/Session/SessionNamespace.php @@ -14,13 +14,6 @@ use IteratorAggregate; */ class SessionNamespace implements IteratorAggregate { - /** - * The session this namespace is associated to - * - * @var Session - */ - protected $session; - /** * The actual values stored in this container * @@ -35,16 +28,6 @@ class SessionNamespace implements IteratorAggregate */ protected $removed = array(); - /** - * Create a new session namespace - * - * @param Session $session The session this namespace is associated to - */ - public function __construct(Session $session = null) - { - $this->session = $session; - } - /** * Return an iterator for all values in this namespace * @@ -197,18 +180,6 @@ class SessionNamespace implements IteratorAggregate } } - /** - * Save the session this namespace is associated to - */ - public function write() - { - if (!$this->session) { - throw new IcingaException('Cannot save, session not set'); - } - - $this->session->write(); - } - /** * Return whether the session namespace has been changed * diff --git a/test/php/library/Icinga/Web/Session/SessionNamespaceTest.php b/test/php/library/Icinga/Web/Session/SessionNamespaceTest.php index 11dcb23ec..90ad6326b 100644 --- a/test/php/library/Icinga/Web/Session/SessionNamespaceTest.php +++ b/test/php/library/Icinga/Web/Session/SessionNamespaceTest.php @@ -4,7 +4,6 @@ namespace Tests\Icinga\Web\Session; -use Mockery; use Icinga\Test\BaseTestCase; use Icinga\Web\Session\SessionNamespace; @@ -85,26 +84,4 @@ class SessionNamespaceTest extends BaseTestCase $this->assertEquals($value, $values[$key]); } } - - /** - * @expectedException Icinga\Exception\IcingaException - * @expectedExceptionMessage Cannot save, session not set - */ - public function testInvalidParentWrite() - { - $ns = new SessionNamespace(); - $ns->write(); - } - - /** - * Check whether it is possible to write a namespace's parent - */ - public function testValidParentWrite() - { - $sessionMock = Mockery::mock('Icinga\Web\Session\Session'); - $sessionMock->shouldReceive('write')->atLeast()->times(1); - - $ns = new SessionNamespace($sessionMock); - $ns->write(); - } } From 635b802a2bd48d43bcbfe87e06f3c076037540fc Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 17 Sep 2014 11:05:35 +0200 Subject: [PATCH 009/318] Add tests for hasChanged, set/getByRef and fix setByRef --- .../Icinga/Web/Session/SessionNamespace.php | 2 +- .../Web/Session/SessionNamespaceTest.php | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Web/Session/SessionNamespace.php b/library/Icinga/Web/Session/SessionNamespace.php index bb67838d6..c72d69c36 100644 --- a/library/Icinga/Web/Session/SessionNamespace.php +++ b/library/Icinga/Web/Session/SessionNamespace.php @@ -111,7 +111,7 @@ class SessionNamespace implements IteratorAggregate public function setByRef($key, &$value) { - $this->values[$key] = $value; + $this->values[$key] = & $value; if (in_array($key, $this->removed)) { unset($this->removed[array_search($key, $this->removed)]); diff --git a/test/php/library/Icinga/Web/Session/SessionNamespaceTest.php b/test/php/library/Icinga/Web/Session/SessionNamespaceTest.php index 90ad6326b..bf984dca0 100644 --- a/test/php/library/Icinga/Web/Session/SessionNamespaceTest.php +++ b/test/php/library/Icinga/Web/Session/SessionNamespaceTest.php @@ -84,4 +84,40 @@ class SessionNamespaceTest extends BaseTestCase $this->assertEquals($value, $values[$key]); } } + + public function testRetrievingValuesByReferenceWorks() + { + $ns = new SessionNamespace(); + $ns->array = array(1, 2); + $array = & $ns->getByRef('array'); + $array[0] = 11; + + $this->assertEquals( + array(11, 2), + $ns->array, + 'Values retrieved with getByRef() seem not be affected by external changes' + ); + } + + public function testSettingValuesByReferenceWorks() + { + $ns = new SessionNamespace(); + $array = array(1, 2); + $ns->setByRef('array', $array); + $array[0] = 11; + + $this->assertEquals( + array(11, 2), + $ns->array, + 'Values set with setByRef() seem not to receive external changes' + ); + } + + public function testTrackingChangesWorks() + { + $ns = new SessionNamespace(); + $this->assertFalse($ns->hasChanged(), 'A new empty session namespace seems to have changes'); + $ns->test = 1; + $this->assertTrue($ns->hasChanged(), 'A new session namespace with values seems not to have changes'); + } } From 028a6c442bff6a35d2259662fac08a7023b620a7 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 17 Sep 2014 11:11:10 +0200 Subject: [PATCH 010/318] Fix persisting notification messages in the session --- library/Icinga/Web/Notification.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/library/Icinga/Web/Notification.php b/library/Icinga/Web/Notification.php index b9cc839ab..c61076cff 100644 --- a/library/Icinga/Web/Notification.php +++ b/library/Icinga/Web/Notification.php @@ -75,13 +75,11 @@ class Notification return; } - $mo = (object) array( + $messages = Session::getSession()->getByRef('messages'); + $messages[] = (object) array( 'type' => $type, 'message' => $message, ); - - $session = Session::getSession(); - $session->messages[] = $mo; } public function hasMessages() From 8e48853f4d18a745da25d10f61f431861df7ef69 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 17 Sep 2014 15:33:17 +0200 Subject: [PATCH 011/318] Add Wizard This is a container/controller for form based wizards. Can be used directly or by using a subclass that allows for a more customized handling. refs #7163 --- library/Icinga/Web/Wizard.php | 479 ++++++++++++++++++++++++++++++++++ 1 file changed, 479 insertions(+) create mode 100644 library/Icinga/Web/Wizard.php diff --git a/library/Icinga/Web/Wizard.php b/library/Icinga/Web/Wizard.php new file mode 100644 index 000000000..dae2ffa63 --- /dev/null +++ b/library/Icinga/Web/Wizard.php @@ -0,0 +1,479 @@ +init(); + } + + /** + * Run additional initialization routines + * + * Should be implemented by subclasses to add pages to the wizard. + */ + protected function init() + { + + } + + /** + * Return the pages being part of this wizard + * + * @return array + */ + public function getPages() + { + return $this->pages; + } + + /** + * Return the page with the given name + * + * @param string $name The name of the page to return + * + * @return null|Form The page or null in case there is no page with the given name + */ + public function getPage($name) + { + foreach ($this->getPages() as $page) { + if ($name === $page->getName()) { + return $page; + } + } + } + + /** + * Add a new page to this wizard + * + * @param Form $page The page to add to the wizard + * + * @return self + */ + public function addPage(Form $page) + { + $this->pages[] = $page; + return $this; + } + + /** + * Add multiple pages to this wizard + * + * @param array $pages The pages to add to the wizard + * + * @return self + */ + public function addPages(array $pages) + { + foreach ($pages as $page) { + $this->addPage($page); + } + + return $this; + } + + /** + * Assert that this wizard has any pages + * + * @throws LogicException In case this wizard has no pages + */ + protected function assertHasPages() + { + $pages = $this->getPages(); + if (empty($pages)) { + throw new LogicException('Although Chuck Norris can advance a wizard without any pages, you can\'t.'); + } + } + + /** + * Return the current page of this wizard + * + * @return Form + * + * @throws LogicException In case the name of the current page currently being set is invalid + */ + public function getCurrentPage() + { + if ($this->currentPage === null) { + $this->assertHasPages(); + $pages = $this->getPages(); + $this->currentPage = $this->getSession()->get('current_page', $pages[0]->getName()); + } + + if (($page = $this->getPage($this->currentPage)) === null) { + throw new LogicException(sprintf('No page found with name "%s"', $this->currentPage)); + } + + return $page; + } + + /** + * Set the current page of this wizard + * + * @param Form $page The page to set as current page + * + * @return self + */ + public function setCurrentPage(Form $page) + { + $this->currentPage = $page->getName(); + $this->getSession()->set('current_page', $this->currentPage); + return $this; + } + + /** + * Setup the given page that is either going to be displayed or validated + * + * Implement this method in a subclass to populate default values and/or other data required to process the form. + * + * @param Form $page The page to setup + * @param Request $request The current request + */ + public function setupPage(Form $page, Request $request) + { + + } + + /** + * Process the given request using this wizard + * + * Validate the request data using the current page, update the wizard's + * position and redirect to the page's redirect url upon success. + * + * @param Request $request The request to be processed + * + * @return Request The request supposed to be processed + */ + public function handleRequest(Request $request = null) + { + $page = $this->getCurrentPage(); + + if ($request === null) { + $request = $page->getRequest(); + } + + $this->setupPage($page, $request); + $requestData = $page->getRequestData($request); + if ($page->wasSent($requestData)) { + if (($requestedPage = $this->getRequestedPage($requestData)) !== null) { + $isValid = false; + $direction = $this->getDirection($request); + if ($direction === static::FORWARD && $page->isValid($requestData)) { + $isValid = true; + if ($this->isLastPage($page)) { + $this->setIsFinished(); + } + } elseif ($direction === static::BACKWARD) { + $isValid = true; + } + + if ($isValid) { + $pageData = & $this->getPageData(); + $pageData[$page->getName()] = $requestData; + $this->setCurrentPage($this->getNewPage($requestedPage)); + $page->getResponse()->redirectAndExit($page->getRedirectUrl()); + } + } else { + $page->isValidPartial($requestData); + } + } elseif (($pageData = $this->getPageData($page->getName())) !== null) { + $page->populate($pageData); + } + + return $request; + } + + /** + * Return the name of the requested page + * + * @param array $requestData The request's data + * + * @return null|string The name of the requested page or null in case no page has been requested + */ + protected function getRequestedPage(array $requestData) + { + if (isset($requestData[static::BTN_NEXT])) { + return $requestData[static::BTN_NEXT]; + } elseif (isset($requestData[static::BTN_PREV])) { + return $requestData[static::BTN_PREV]; + } + } + + /** + * Return the direction of this wizard using the given request + * + * @param Request $request The request to use + * + * @return int The direction @see Wizard::FORWARD @see Wizard::BACKWARD @see Wizard::NO_CHANGE + */ + protected function getDirection(Request $request = null) + { + $currentPage = $this->getCurrentPage(); + + if ($request === null) { + $request = $currentPage->getRequest(); + } + + $requestData = $currentPage->getRequestData($request); + if (isset($requestData[static::BTN_NEXT])) { + return static::FORWARD; + } elseif (isset($requestData[static::BTN_PREV])) { + return static::BACKWARD; + } + + return static::NO_CHANGE; + } + + /** + * Return the new page to set as current page + * + * @param string $requestedPage The name of the requested page + * + * @return Form The new page + * + * @throws InvalidArgumentException In case the requested page does not exist or is not permitted yet + */ + protected function getNewPage($requestedPage) + { + if (($page = $this->getPage($requestedPage)) !== null) { + $permitted = true; + + $pages = $this->getPages(); + if (($index = array_search($page, $pages, true)) > 0) { + $permitted = $this->hasPageData($pages[$index - 1]->getName()); + } + + if ($permitted) { + return $page; + } + } + + throw new InvalidArgumentException( + sprintf('"%s" is either an unknown page or one you are not permitted to view', $requestedPage) + ); + } + + /** + * Return whether the given page is this wizard's last page + * + * @param Form $page The page to check + * + * @return bool + */ + protected function isLastPage(Form $page) + { + return $page->getName() === end($this->getPages())->getName(); + } + + /** + * Set whether this wizard has been completed + * + * @param bool $state Whether this wizard has been completed + * + * @return self + */ + public function setIsFinished($state = true) + { + $this->getSession()->set('isFinished', $state); + return $this; + } + + /** + * Return whether this wizard has been completed + * + * @return bool + */ + public function isFinished() + { + return $this->getSession()->get('isFinished', false); + } + + /** + * Return the overall page data or one for a particular page + * + * Note that this method returns by reference so in order to update the + * returned array set this method's return value also by reference. + * + * @param string $pageName The page for which to return the data + * + * @return array + */ + public function & getPageData($pageName = null) + { + $session = $this->getSession(); + + if (false === isset($session->page_data)) { + $session->page_data = array(); + } + + $pageData = & $session->getByRef('page_data'); + if ($pageName !== null) { + $data = null; + if (isset($pageData[$pageName])) { + $data = & $pageData[$pageName]; + } + + return $data; + } + + return $pageData; + } + + /** + * Return whether there is any data for the given page + * + * @param string $pageName The name of the page to check + * + * @return bool + */ + public function hasPageData($pageName) + { + return $this->getPageData($pageName) !== null; + } + + /** + * Return a session to be used by this wizard + * + * @return SessionNamespace + */ + public function getSession() + { + return Session::getSession()->getNamespace(get_class($this)); + } + + /** + * Add buttons to the given page based on its position in the page-chain + * + * @param Form $page The page to add the buttons to + */ + protected function addButtons(Form $page) + { + $pages = $this->getPages(); + $index = array_search($page, $pages, true); + if ($index === 0) { + $page->addElement( + 'button', + static::BTN_NEXT, + array( + 'type' => 'submit', + 'value' => $pages[1]->getName(), + 'label' => t('Next') + ) + ); + } elseif ($index < count($pages) - 1) { + $page->addElement( + 'button', + static::BTN_PREV, + array( + 'type' => 'submit', + 'value' => $pages[$index - 1]->getName(), + 'label' => t('Back') + ) + ); + $page->addElement( + 'button', + static::BTN_NEXT, + array( + 'type' => 'submit', + 'value' => $pages[$index + 1]->getName(), + 'label' => t('Next') + ) + ); + } else { + $page->addElement( + 'button', + static::BTN_PREV, + array( + 'type' => 'submit', + 'value' => $pages[$index - 1]->getName(), + 'label' => t('Back') + ) + ); + $page->addElement( + 'button', + static::BTN_NEXT, + array( + 'type' => 'submit', + 'value' => $page->getName(), + 'label' => t('Finish') + ) + ); + } + } + + /** + * Return the current page of this wizard with appropriate buttons being added + * + * @return Form + */ + public function getForm() + { + $form = $this->getCurrentPage(); + $form->create(); // Make sure that buttons are displayed at the very bottom + $this->addButtons($form); + return $form; + } + + /** + * Return the current page of this wizard rendered as HTML + * + * @return string + */ + public function __toString() + { + return $this->getForm()->render(); + } +} From 0e92e333aa0585302cb1d078fe855241b45289ae Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 11:02:45 +0200 Subject: [PATCH 012/318] Make isValid* calls of configuration forms being static This allows a more flexible usage as there is no need to access instance formdata when validating such configuration. --- .../forms/Config/Authentication/AutologinBackendForm.php | 2 +- application/forms/Config/Authentication/DbBackendForm.php | 4 ++-- .../forms/Config/Authentication/LdapBackendForm.php | 4 ++-- .../forms/Config/AuthenticationBackendConfigForm.php | 2 +- application/forms/Config/Resource/DbResourceForm.php | 4 ++-- application/forms/Config/Resource/LdapResourceForm.php | 4 ++-- .../forms/Config/Resource/LivestatusResourceForm.php | 4 ++-- application/forms/Config/ResourceConfigForm.php | 2 +- .../forms/Config/Authentication/DbBackendFormTest.php | 4 ++-- .../forms/Config/Authentication/LdapBackendFormTest.php | 4 ++-- .../forms/Config/Resource/DbResourceFormTest.php | 8 ++------ .../forms/Config/Resource/LdapResourceFormTest.php | 8 ++------ .../forms/Config/Resource/LivestatusResourceFormTest.php | 8 ++------ 13 files changed, 23 insertions(+), 35 deletions(-) diff --git a/application/forms/Config/Authentication/AutologinBackendForm.php b/application/forms/Config/Authentication/AutologinBackendForm.php index 4f4df73e3..05e1f201d 100644 --- a/application/forms/Config/Authentication/AutologinBackendForm.php +++ b/application/forms/Config/Authentication/AutologinBackendForm.php @@ -82,7 +82,7 @@ class AutologinBackendForm extends Form * * @return bool Whether validation succeeded or not */ - public function isValidAuthenticationBackend(Form $form) + public static function isValidAuthenticationBackend(Form $form) { return true; } diff --git a/application/forms/Config/Authentication/DbBackendForm.php b/application/forms/Config/Authentication/DbBackendForm.php index 23335f8fa..9e000753c 100644 --- a/application/forms/Config/Authentication/DbBackendForm.php +++ b/application/forms/Config/Authentication/DbBackendForm.php @@ -88,7 +88,7 @@ class DbBackendForm extends Form */ public function onSuccess(Request $request) { - if (false === $this->isValidAuthenticationBackend($this)) { + if (false === static::isValidAuthenticationBackend($this)) { return false; } } @@ -100,7 +100,7 @@ class DbBackendForm extends Form * * @return bool Whether validation succeeded or not */ - public function isValidAuthenticationBackend(Form $form) + public static function isValidAuthenticationBackend(Form $form) { $element = $form->getElement('resource'); diff --git a/application/forms/Config/Authentication/LdapBackendForm.php b/application/forms/Config/Authentication/LdapBackendForm.php index 6056b0911..a93141f37 100644 --- a/application/forms/Config/Authentication/LdapBackendForm.php +++ b/application/forms/Config/Authentication/LdapBackendForm.php @@ -108,7 +108,7 @@ class LdapBackendForm extends Form */ public function onSuccess(Request $request) { - if (false === $this->isValidAuthenticationBackend($this)) { + if (false === static::isValidAuthenticationBackend($this)) { return false; } } @@ -120,7 +120,7 @@ class LdapBackendForm extends Form * * @return bool Whether validation succeeded or not */ - public function isValidAuthenticationBackend(Form $form) + public static function isValidAuthenticationBackend(Form $form) { $element = $form->getElement('resource'); diff --git a/application/forms/Config/AuthenticationBackendConfigForm.php b/application/forms/Config/AuthenticationBackendConfigForm.php index e81382cd4..1e893fd00 100644 --- a/application/forms/Config/AuthenticationBackendConfigForm.php +++ b/application/forms/Config/AuthenticationBackendConfigForm.php @@ -201,7 +201,7 @@ class AuthenticationBackendConfigForm extends ConfigForm { if (($el = $this->getElement('force_creation')) === null || false === $el->isChecked()) { $backendForm = $this->getBackendForm($this->getElement('type')->getValue()); - if (false === $backendForm->isValidAuthenticationBackend($this)) { + if (false === $backendForm::isValidAuthenticationBackend($this)) { $this->addElement($this->getForceCreationCheckbox()); return false; } diff --git a/application/forms/Config/Resource/DbResourceForm.php b/application/forms/Config/Resource/DbResourceForm.php index 4fd7297d9..686013386 100644 --- a/application/forms/Config/Resource/DbResourceForm.php +++ b/application/forms/Config/Resource/DbResourceForm.php @@ -103,7 +103,7 @@ class DbResourceForm extends Form */ public function onSuccess(Request $request) { - if (false === $this->isValidResource($this)) { + if (false === static::isValidResource($this)) { return false; } } @@ -115,7 +115,7 @@ class DbResourceForm extends Form * * @return bool Whether validation succeeded or not */ - public function isValidResource(Form $form) + public static function isValidResource(Form $form) { try { $resource = ResourceFactory::createResource(new Zend_Config($form->getValues())); diff --git a/application/forms/Config/Resource/LdapResourceForm.php b/application/forms/Config/Resource/LdapResourceForm.php index 6196f3a17..6dacc6bea 100644 --- a/application/forms/Config/Resource/LdapResourceForm.php +++ b/application/forms/Config/Resource/LdapResourceForm.php @@ -89,7 +89,7 @@ class LdapResourceForm extends Form */ public function onSuccess(Request $request) { - if (false === $this->isValidResource($this)) { + if (false === static::isValidResource($this)) { return false; } } @@ -101,7 +101,7 @@ class LdapResourceForm extends Form * * @return bool Whether validation succeeded or not */ - public function isValidResource(Form $form) + public static function isValidResource(Form $form) { try { $resource = ResourceFactory::createResource(new Zend_Config($form->getValues())); diff --git a/application/forms/Config/Resource/LivestatusResourceForm.php b/application/forms/Config/Resource/LivestatusResourceForm.php index 92534f220..3314ca284 100644 --- a/application/forms/Config/Resource/LivestatusResourceForm.php +++ b/application/forms/Config/Resource/LivestatusResourceForm.php @@ -50,7 +50,7 @@ class LivestatusResourceForm extends Form */ public function onSuccess(Request $request) { - if (false === $this->isValidResource($this)) { + if (false === static::isValidResource($this)) { return false; } } @@ -62,7 +62,7 @@ class LivestatusResourceForm extends Form * * @return bool Whether validation succeeded or not */ - public function isValidResource(Form $form) + public static function isValidResource(Form $form) { try { $resource = ResourceFactory::createResource(new Zend_Config($form->getValues())); diff --git a/application/forms/Config/ResourceConfigForm.php b/application/forms/Config/ResourceConfigForm.php index 8e677cf8b..51677b12f 100644 --- a/application/forms/Config/ResourceConfigForm.php +++ b/application/forms/Config/ResourceConfigForm.php @@ -135,7 +135,7 @@ class ResourceConfigForm extends ConfigForm { if (($el = $this->getElement('force_creation')) === null || false === $el->isChecked()) { $resourceForm = $this->getResourceForm($this->getElement('type')->getValue()); - if (method_exists($resourceForm, 'isValidResource') && false === $resourceForm->isValidResource($this)) { + if (method_exists($resourceForm, 'isValidResource') && false === $resourceForm::isValidResource($this)) { $this->addElement($this->getForceCreationCheckbox()); return false; } diff --git a/test/php/application/forms/Config/Authentication/DbBackendFormTest.php b/test/php/application/forms/Config/Authentication/DbBackendFormTest.php index 3364dff60..7474d2703 100644 --- a/test/php/application/forms/Config/Authentication/DbBackendFormTest.php +++ b/test/php/application/forms/Config/Authentication/DbBackendFormTest.php @@ -37,7 +37,7 @@ class DbBackendFormTest extends BaseTestCase $form->populate(array('resource' => 'test_db_backend')); $this->assertTrue( - $form->isValidAuthenticationBackend($form), + DbBackendForm::isValidAuthenticationBackend($form), 'DbBackendForm claims that a valid authentication backend with users is not valid' ); } @@ -59,7 +59,7 @@ class DbBackendFormTest extends BaseTestCase $form->populate(array('resource' => 'test_db_backend')); $this->assertFalse( - $form->isValidAuthenticationBackend($form), + DbBackendForm::isValidAuthenticationBackend($form), 'DbBackendForm claims that an invalid authentication backend without users is valid' ); } diff --git a/test/php/application/forms/Config/Authentication/LdapBackendFormTest.php b/test/php/application/forms/Config/Authentication/LdapBackendFormTest.php index 0335d1f82..c38f26376 100644 --- a/test/php/application/forms/Config/Authentication/LdapBackendFormTest.php +++ b/test/php/application/forms/Config/Authentication/LdapBackendFormTest.php @@ -37,7 +37,7 @@ class LdapBackendFormTest extends BaseTestCase $form->populate(array('resource' => 'test_ldap_backend')); $this->assertTrue( - $form->isValidAuthenticationBackend($form), + LdapBackendForm::isValidAuthenticationBackend($form), 'LdapBackendForm claims that a valid authentication backend with users is not valid' ); } @@ -58,7 +58,7 @@ class LdapBackendFormTest extends BaseTestCase $form->populate(array('resource' => 'test_ldap_backend')); $this->assertFalse( - $form->isValidAuthenticationBackend($form), + LdapBackendForm::isValidAuthenticationBackend($form), 'LdapBackendForm claims that an invalid authentication backend without users is valid' ); } diff --git a/test/php/application/forms/Config/Resource/DbResourceFormTest.php b/test/php/application/forms/Config/Resource/DbResourceFormTest.php index 1b624c49f..a962375b5 100644 --- a/test/php/application/forms/Config/Resource/DbResourceFormTest.php +++ b/test/php/application/forms/Config/Resource/DbResourceFormTest.php @@ -30,10 +30,8 @@ class DbResourceFormTest extends BaseTestCase Mockery::mock()->shouldReceive('getConnection')->atMost()->twice()->andReturn(Mockery::self())->getMock() ); - $form = new DbResourceForm(); - $this->assertTrue( - $form->isValidResource($form), + DbResourceForm::isValidResource(new DbResourceForm()), 'ResourceForm claims that a valid db resource is not valid' ); } @@ -48,10 +46,8 @@ class DbResourceFormTest extends BaseTestCase Mockery::mock()->shouldReceive('getConnection')->once()->andThrow('\Exception')->getMock() ); - $form = new DbResourceForm(); - $this->assertFalse( - $form->isValidResource($form), + DbResourceForm::isValidResource(new DbResourceForm()), 'ResourceForm claims that an invalid db resource is valid' ); } diff --git a/test/php/application/forms/Config/Resource/LdapResourceFormTest.php b/test/php/application/forms/Config/Resource/LdapResourceFormTest.php index 078a11146..b17a01a06 100644 --- a/test/php/application/forms/Config/Resource/LdapResourceFormTest.php +++ b/test/php/application/forms/Config/Resource/LdapResourceFormTest.php @@ -30,10 +30,8 @@ class LdapResourceFormTest extends BaseTestCase Mockery::mock()->shouldReceive('connect')->getMock() ); - $form = new LdapResourceForm(); - $this->assertTrue( - $form->isValidResource($form), + LdapResourceForm::isValidResource(new LdapResourceForm()), 'ResourceForm claims that a valid ldap resource is not valid' ); } @@ -48,10 +46,8 @@ class LdapResourceFormTest extends BaseTestCase Mockery::mock()->shouldReceive('connect')->once()->andThrow('\Exception')->getMock() ); - $form = new LdapResourceForm(); - $this->assertFalse( - $form->isValidResource($form), + LdapResourceForm::isValidResource(new LdapResourceForm()), 'ResourceForm claims that an invalid ldap resource is valid' ); } diff --git a/test/php/application/forms/Config/Resource/LivestatusResourceFormTest.php b/test/php/application/forms/Config/Resource/LivestatusResourceFormTest.php index 300adebed..5748d7c53 100644 --- a/test/php/application/forms/Config/Resource/LivestatusResourceFormTest.php +++ b/test/php/application/forms/Config/Resource/LivestatusResourceFormTest.php @@ -31,10 +31,8 @@ class LivestatusResourceFormTest extends BaseTestCase ->shouldReceive('disconnect')->getMock() ); - $form = new LivestatusResourceForm(); - $this->assertTrue( - $form->isValidResource($form), + LivestatusResourceForm::isValidResource(new LivestatusResourceForm()), 'ResourceForm claims that a valid livestatus resource is not valid' ); } @@ -49,10 +47,8 @@ class LivestatusResourceFormTest extends BaseTestCase Mockery::mock()->shouldReceive('connect')->once()->andThrow('\Exception')->getMock() ); - $form = new LivestatusResourceForm(); - $this->assertFalse( - $form->isValidResource($form), + LivestatusResourceForm::isValidResource(new LivestatusResourceForm()), 'ResourceForm claims that an invalid livestatus resource is valid' ); } From 4f688fa54483cba52d51fb935fda84821e39b00c Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 11:06:16 +0200 Subject: [PATCH 013/318] Let the form decide where to get the resource configuration from refs #7163 --- .../Config/Authentication/DbBackendForm.php | 18 ++++++++++++----- .../Config/Authentication/LdapBackendForm.php | 20 +++++++++++++++---- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/application/forms/Config/Authentication/DbBackendForm.php b/application/forms/Config/Authentication/DbBackendForm.php index 9e000753c..de86d8b24 100644 --- a/application/forms/Config/Authentication/DbBackendForm.php +++ b/application/forms/Config/Authentication/DbBackendForm.php @@ -102,19 +102,27 @@ class DbBackendForm extends Form */ public static function isValidAuthenticationBackend(Form $form) { - $element = $form->getElement('resource'); - try { - $dbUserBackend = new DbUserBackend(ResourceFactory::create($element->getValue())); + $dbUserBackend = new DbUserBackend(ResourceFactory::createResource($form->getResourceConfig())); if ($dbUserBackend->count() < 1) { - $element->addError(t('No users found under the specified database backend')); + $form->addError(t('No users found under the specified database backend')); return false; } } catch (Exception $e) { - $element->addError(sprintf(t('Using the specified backend failed: %s'), $e->getMessage())); + $form->addError(sprintf(t('Using the specified backend failed: %s'), $e->getMessage())); return false; } return true; } + + /** + * Return the configuration for the chosen resource + * + * @return Zend_Config + */ + public function getResourceConfig() + { + return ResourceFactory::getResourceConfig($this->getValue('resource')); + } } diff --git a/application/forms/Config/Authentication/LdapBackendForm.php b/application/forms/Config/Authentication/LdapBackendForm.php index a93141f37..32fcfad26 100644 --- a/application/forms/Config/Authentication/LdapBackendForm.php +++ b/application/forms/Config/Authentication/LdapBackendForm.php @@ -8,6 +8,7 @@ use Exception; use Icinga\Web\Form; use Icinga\Web\Request; use Icinga\Data\ResourceFactory; +use Icinga\Exception\AuthenticationException; use Icinga\Authentication\Backend\LdapUserBackend; /** @@ -122,20 +123,31 @@ class LdapBackendForm extends Form */ public static function isValidAuthenticationBackend(Form $form) { - $element = $form->getElement('resource'); - try { $ldapUserBackend = new LdapUserBackend( - ResourceFactory::create($element->getValue()), + ResourceFactory::createResource($form->getResourceConfig()), $form->getElement('user_class')->getValue(), $form->getElement('user_name_attribute')->getValue() ); $ldapUserBackend->assertAuthenticationPossible(); + } catch (AuthenticationException $e) { + $form->addError($e->getMessage()); + return false; } catch (Exception $e) { - $element->addError(sprintf(t('Connection validation failed: %s'), $e->getMessage())); + $form->addError(sprintf(t('Unable to validate authentication: %s'), $e->getMessage())); return false; } return true; } + + /** + * Return the configuration for the chosen resource + * + * @return Zend_Config + */ + public function getResourceConfig() + { + return ResourceFactory::getResourceConfig($this->getValue('resource')); + } } From 380ffe45b9ce861fbe744eafc217dbe285c10135 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 11:20:39 +0200 Subject: [PATCH 014/318] Add some redundancy to get more implicitness Although this commit does not seem reasonable at first the "big" picture proves its necessity! refs #7163 --- application/forms/Config/Resource/DbResourceForm.php | 9 +++++++++ application/forms/Config/Resource/FileResourceForm.php | 9 +++++++++ application/forms/Config/Resource/LdapResourceForm.php | 9 +++++++++ .../forms/Config/Resource/LivestatusResourceForm.php | 9 +++++++++ .../forms/Config/Resource/StatusdatResourceForm.php | 9 +++++++++ application/forms/Config/ResourceConfigForm.php | 9 --------- 6 files changed, 45 insertions(+), 9 deletions(-) diff --git a/application/forms/Config/Resource/DbResourceForm.php b/application/forms/Config/Resource/DbResourceForm.php index 686013386..540a69813 100644 --- a/application/forms/Config/Resource/DbResourceForm.php +++ b/application/forms/Config/Resource/DbResourceForm.php @@ -29,6 +29,15 @@ class DbResourceForm extends Form */ public function createElements(array $formData) { + $this->addElement( + 'text', + 'name', + array( + 'required' => true, + 'label' => t('Resource Name'), + 'description' => t('The unique name of this resource') + ) + ); $this->addElement( 'select', 'db', diff --git a/application/forms/Config/Resource/FileResourceForm.php b/application/forms/Config/Resource/FileResourceForm.php index d1d33a749..2814e9a72 100644 --- a/application/forms/Config/Resource/FileResourceForm.php +++ b/application/forms/Config/Resource/FileResourceForm.php @@ -25,6 +25,15 @@ class FileResourceForm extends Form */ public function createElements(array $formData) { + $this->addElement( + 'text', + 'name', + array( + 'required' => true, + 'label' => t('Resource Name'), + 'description' => t('The unique name of this resource') + ) + ); $this->addElement( 'text', 'filename', diff --git a/application/forms/Config/Resource/LdapResourceForm.php b/application/forms/Config/Resource/LdapResourceForm.php index 6dacc6bea..5f46f64b2 100644 --- a/application/forms/Config/Resource/LdapResourceForm.php +++ b/application/forms/Config/Resource/LdapResourceForm.php @@ -29,6 +29,15 @@ class LdapResourceForm extends Form */ public function createElements(array $formData) { + $this->addElement( + 'text', + 'name', + array( + 'required' => true, + 'label' => t('Resource Name'), + 'description' => t('The unique name of this resource') + ) + ); $this->addElement( 'text', 'hostname', diff --git a/application/forms/Config/Resource/LivestatusResourceForm.php b/application/forms/Config/Resource/LivestatusResourceForm.php index 3314ca284..cfbd38c98 100644 --- a/application/forms/Config/Resource/LivestatusResourceForm.php +++ b/application/forms/Config/Resource/LivestatusResourceForm.php @@ -29,6 +29,15 @@ class LivestatusResourceForm extends Form */ public function createElements(array $formData) { + $this->addElement( + 'text', + 'name', + array( + 'required' => true, + 'label' => t('Resource Name'), + 'description' => t('The unique name of this resource') + ) + ); $this->addElement( 'text', 'socket', diff --git a/application/forms/Config/Resource/StatusdatResourceForm.php b/application/forms/Config/Resource/StatusdatResourceForm.php index f150056d1..d440f139d 100644 --- a/application/forms/Config/Resource/StatusdatResourceForm.php +++ b/application/forms/Config/Resource/StatusdatResourceForm.php @@ -26,6 +26,15 @@ class StatusdatResourceForm extends Form */ public function createElements(array $formData) { + $this->addElement( + 'text', + 'name', + array( + 'required' => true, + 'label' => t('Resource Name'), + 'description' => t('The unique name of this resource') + ) + ); $this->addElement( 'text', 'status_file', diff --git a/application/forms/Config/ResourceConfigForm.php b/application/forms/Config/ResourceConfigForm.php index 51677b12f..cef9310be 100644 --- a/application/forms/Config/ResourceConfigForm.php +++ b/application/forms/Config/ResourceConfigForm.php @@ -224,15 +224,6 @@ class ResourceConfigForm extends ConfigForm $resourceTypes['db'] = t('SQL Database'); } - $this->addElement( - 'text', - 'name', - array( - 'required' => true, - 'label' => t('Resource Name'), - 'description' => t('The unique name of this resource') - ) - ); $this->addElement( 'select', 'type', From 7e00e83772b425dc1042b217da2b21c8b275cb46 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 11:21:15 +0200 Subject: [PATCH 015/318] Make the loglevel ERROR being the default refs #7163 --- application/forms/Config/General/LoggingConfigForm.php | 1 + 1 file changed, 1 insertion(+) diff --git a/application/forms/Config/General/LoggingConfigForm.php b/application/forms/Config/General/LoggingConfigForm.php index 3d07d4eb6..c136e4a65 100644 --- a/application/forms/Config/General/LoggingConfigForm.php +++ b/application/forms/Config/General/LoggingConfigForm.php @@ -27,6 +27,7 @@ class LoggingConfigForm extends Form 'select', 'logging_level', array( + 'value' => 1, 'required' => true, 'label' => t('Logging Level'), 'description' => t('The maximum loglevel to emit.'), From 8fcf21a6b80f353d6f033c7a6123aa22fa94b166 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 11:21:40 +0200 Subject: [PATCH 016/318] Make it possible to retrieve a list of available users for authentication refs #7163 --- .../Authentication/Backend/DbUserBackend.php | 17 +++++++++++++++++ .../Authentication/Backend/LdapUserBackend.php | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/library/Icinga/Authentication/Backend/DbUserBackend.php b/library/Icinga/Authentication/Backend/DbUserBackend.php index a89a07c81..239ceb258 100644 --- a/library/Icinga/Authentication/Backend/DbUserBackend.php +++ b/library/Icinga/Authentication/Backend/DbUserBackend.php @@ -126,4 +126,21 @@ class DbUserBackend extends UserBackend return ($row !== false) ? $row->count : 0; } + + /** + * Return the names of all available users + * + * @return array + */ + public function listUsers() + { + $query = $this->conn->select()->from('account', array('username')); + + $users = array(); + foreach ($query->fetchAll() as $row) { + $users[] = $row->username; + } + + return $users; + } } diff --git a/library/Icinga/Authentication/Backend/LdapUserBackend.php b/library/Icinga/Authentication/Backend/LdapUserBackend.php index 23a79781d..8e5307fc8 100644 --- a/library/Icinga/Authentication/Backend/LdapUserBackend.php +++ b/library/Icinga/Authentication/Backend/LdapUserBackend.php @@ -159,5 +159,22 @@ class LdapUserBackend extends UserBackend ) ); } + + /** + * Return the names of all available users + * + * @return array + */ + public function listUsers() + { + $query = $this->conn->select()->from($this->userClass, array($this->userNameAttribute)); + + $users = array(); + foreach ($query->fetchAll() as $row) { + $users[] = $row->{$this->userNameAttribute}; + } + + return $users; + } } From 5b3d549e5caca9a5f519c01137dab78b2b88d77d Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 11:21:56 +0200 Subject: [PATCH 017/318] Add some more platform related calls refs #7163 --- library/Icinga/Application/Platform.php | 52 ++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/library/Icinga/Application/Platform.php b/library/Icinga/Application/Platform.php index 1ca738741..179e2578c 100644 --- a/library/Icinga/Application/Platform.php +++ b/library/Icinga/Application/Platform.php @@ -30,6 +30,16 @@ class Platform */ protected static $fqdn; + /** + * Return the operating system's name + * + * @return string + */ + public static function getOperatingSystemName() + { + return php_uname('s'); + } + /** * Test of windows * @@ -37,7 +47,7 @@ class Platform */ public static function isWindows() { - return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; + return strtoupper(substr(self::getOperatingSystemName(), 0, 3)) === 'WIN'; } /** @@ -47,7 +57,7 @@ class Platform */ public static function isLinux() { - return strtoupper(substr(PHP_OS, 0, 5)) === 'LINUX'; + return strtoupper(substr(self::getOperatingSystemName(), 0, 5)) === 'LINUX'; } /** @@ -120,6 +130,16 @@ class Platform } } + /** + * Return the version of PHP + * + * @return string + */ + public static function getPhpVersion() + { + return phpversion(); + } + /** * Test for php extension * @@ -131,4 +151,32 @@ class Platform { return extension_loaded($extensionName); } + + /** + * Return the value for the given PHP configuration option + * + * @param string $option The option name for which to return the value + * + * @return string|false + */ + public static function getPhpConfig($option) + { + return ini_get($option); + } + + /** + * Return whether the given Zend framework class exists + * + * @param string $name The name of the class to check + * + * @return bool + */ + public static function zendClassExists($name) + { + if (class_exists($name)) { + return true; + } + + return (@include str_replace('_', '/', $name) . '.php') !== false; + } } From 2c44fe34a7075d808ccc99cf3c97e800cb54ec12 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 11:22:43 +0200 Subject: [PATCH 018/318] Fix that ldap auth validation does not send a BIND request --- application/forms/Config/Resource/LdapResourceForm.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/application/forms/Config/Resource/LdapResourceForm.php b/application/forms/Config/Resource/LdapResourceForm.php index 5f46f64b2..facf5679e 100644 --- a/application/forms/Config/Resource/LdapResourceForm.php +++ b/application/forms/Config/Resource/LdapResourceForm.php @@ -114,7 +114,13 @@ class LdapResourceForm extends Form { try { $resource = ResourceFactory::createResource(new Zend_Config($form->getValues())); - $resource->connect(); + if (false === $resource->testCredentials( + $form->getElement('bind_dn')->getValue(), + $form->getElement('bind_pw')->getValue() + ) + ) { + throw new Exception(); + } } catch (Exception $e) { $form->addError(t('Connectivity validation failed, connection to the given resource not possible.')); return false; From a5454e5702453937956ba1ba1f3fb17f218e9172 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 11:24:23 +0200 Subject: [PATCH 019/318] Improve form element description --- application/forms/Config/AuthenticationBackendConfigForm.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/forms/Config/AuthenticationBackendConfigForm.php b/application/forms/Config/AuthenticationBackendConfigForm.php index 1e893fd00..2e4b1d0fd 100644 --- a/application/forms/Config/AuthenticationBackendConfigForm.php +++ b/application/forms/Config/AuthenticationBackendConfigForm.php @@ -307,7 +307,7 @@ class AuthenticationBackendConfigForm extends ConfigForm 'required' => true, 'autosubmit' => true, 'label' => t('Backend Type'), - 'description' => t('The type of the resource to use for this authenticaton backend'), + 'description' => t('The type of the resource to use for this authenticaton provider'), 'multiOptions' => $backendTypes ) ); From 3107eb87bb763b9cd0f86139fc97dc1587fcd956 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 11:24:59 +0200 Subject: [PATCH 020/318] Silence stat() call --- library/Icinga/Web/Form/Validator/TokenValidator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Web/Form/Validator/TokenValidator.php b/library/Icinga/Web/Form/Validator/TokenValidator.php index eb07d5438..e55369fd6 100644 --- a/library/Icinga/Web/Form/Validator/TokenValidator.php +++ b/library/Icinga/Web/Form/Validator/TokenValidator.php @@ -44,7 +44,7 @@ class TokenValidator extends Zend_Validate_Abstract */ public function isValid($value, $context = null) { - $tokenStats = stat($this->tokenPath); + $tokenStats = @stat($this->tokenPath); if (($tokenStats['mode'] & 4) === 4) { $this->_error('TOKEN_FILE_PUBLIC'); return false; From 73b41769e7945f09f37451d2cc58afd6e6e9d5de Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 11:28:58 +0200 Subject: [PATCH 021/318] __toString() should call __toString() as well if possible There may be cases that an error occurs and another _toString() implementation handles that appropriately. This is such a case. --- library/Icinga/Web/Wizard.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Web/Wizard.php b/library/Icinga/Web/Wizard.php index dae2ffa63..916dc1112 100644 --- a/library/Icinga/Web/Wizard.php +++ b/library/Icinga/Web/Wizard.php @@ -474,6 +474,6 @@ class Wizard */ public function __toString() { - return $this->getForm()->render(); + return (string) $this->getForm(); } } From 5c13a19b1ae65769b03903690f829c8cd7b0c9db Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 11:31:13 +0200 Subject: [PATCH 022/318] Populate a wizard page in case the user moves backwards refs #7163 --- library/Icinga/Web/Wizard.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Web/Wizard.php b/library/Icinga/Web/Wizard.php index 916dc1112..1e0c8c49d 100644 --- a/library/Icinga/Web/Wizard.php +++ b/library/Icinga/Web/Wizard.php @@ -217,12 +217,13 @@ class Wizard $this->setIsFinished(); } } elseif ($direction === static::BACKWARD) { + $page->populate($requestData); $isValid = true; } if ($isValid) { $pageData = & $this->getPageData(); - $pageData[$page->getName()] = $requestData; + $pageData[$page->getName()] = $page->getValues(); $this->setCurrentPage($this->getNewPage($requestedPage)); $page->getResponse()->redirectAndExit($page->getRedirectUrl()); } From 7c53e6f7bee0424cf315d6f8af3cf73838d370b9 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 11:31:59 +0200 Subject: [PATCH 023/318] Improve page permission handling refs #7163 --- library/Icinga/Web/Wizard.php | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/library/Icinga/Web/Wizard.php b/library/Icinga/Web/Wizard.php index 1e0c8c49d..3a69caf15 100644 --- a/library/Icinga/Web/Wizard.php +++ b/library/Icinga/Web/Wizard.php @@ -224,7 +224,7 @@ class Wizard if ($isValid) { $pageData = & $this->getPageData(); $pageData[$page->getName()] = $page->getValues(); - $this->setCurrentPage($this->getNewPage($requestedPage)); + $this->setCurrentPage($this->getNewPage($requestedPage, $page)); $page->getResponse()->redirectAndExit($page->getRedirectUrl()); } } else { @@ -281,20 +281,30 @@ class Wizard /** * Return the new page to set as current page * + * Permission is checked by verifying that the requested page's previous page has page data available. + * The requested page is automatically permitted without any checks if the origin page is its previous + * page or one that occurs later in order. + * * @param string $requestedPage The name of the requested page + * @param Form $originPage The origin page * * @return Form The new page * * @throws InvalidArgumentException In case the requested page does not exist or is not permitted yet */ - protected function getNewPage($requestedPage) + protected function getNewPage($requestedPage, Form $originPage) { if (($page = $this->getPage($requestedPage)) !== null) { $permitted = true; $pages = $this->getPages(); if (($index = array_search($page, $pages, true)) > 0) { - $permitted = $this->hasPageData($pages[$index - 1]->getName()); + $previousPage = $pages[$index - 1]; + if ($originPage === null || ($previousPage->getName() !== $originPage->getName() + && array_search($originPage, $pages, true) < $index)) + { + $permitted = $this->hasPageData($previousPage->getName()); + } } if ($permitted) { From aa17f2828dd45872934d53d99888506a83fcbedf Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 12:24:56 +0200 Subject: [PATCH 024/318] Add utility to ease working with databases without the use of Zend refs #7163 --- library/Icinga/Web/Setup/DbTool.php | 158 ++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 library/Icinga/Web/Setup/DbTool.php diff --git a/library/Icinga/Web/Setup/DbTool.php b/library/Icinga/Web/Setup/DbTool.php new file mode 100644 index 000000000..895065540 --- /dev/null +++ b/library/Icinga/Web/Setup/DbTool.php @@ -0,0 +1,158 @@ +config = $config; + } + + /** + * Connect to the server + */ + public function connectToHost() + { + $this->assertHostAccess(); + $this->connect(); + } + + /** + * Connect to the database + */ + public function connectToDb() + { + $this->assertHostAccess(); + $this->assertDatabaseAccess(); + $this->connect($this->config['dbname']); + } + + /** + * Assert that all configuration values exist that are required to connect to a server + * + * @throws ConfigurationError + */ + protected function assertHostAccess() + { + if (false === isset($this->config['db'])) { + throw new ConfigurationError('Can\'t connect to database server of unknown type'); + } elseif (false === isset($this->config['host'])) { + throw new ConfigurationError('Can\'t connect to database server without a hostname or address'); + } elseif (false === isset($this->config['port'])) { + throw new ConfigurationError('Can\'t connect to database server without a port'); + } elseif (false === isset($this->config['username'])) { + throw new ConfigurationError('Can\'t connect to database server without a username'); + } elseif (false === isset($this->config['password'])) { + throw new ConfigurationError('Can\'t connect to database server without a password'); + } + } + + /** + * Assert that all configuration values exist that are required to connect to a database + * + * @throws ConfigurationError + */ + protected function assertDatabaseAccess() + { + if (false === isset($this->config['dbname'])) { + throw new ConfigurationError('Can\'t connect to database without a valid database name'); + } + } + + /** + * Establish a connection with the database or just the server by omitting the database name + * + * @param string $dbname The name of the database to connect to + */ + public function connect($dbname = null) + { + if ($this->conn !== null) { + return; + } + + $this->conn = new PDO( + $this->buildDsn($this->config['db'], $dbname), + $this->config['username'], + $this->config['password'], + array(PDO::ATTR_TIMEOUT => 1, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION) + ); + } + + /** + * Return a datasource name for the given database type and name + * + * @param string $dbtype + * @param string $dbname + * + * @return string + * + * @throws ConfigurationError In case the passed database type is not supported + */ + protected function buildDsn($dbtype, $dbname = null) + { + if ($dbtype === 'mysql') { + return 'mysql:host=' . $this->config['host'] . ';port=' . $this->config['port'] + . ($dbname !== null ? ';dbname=' . $dbname : ''); + } elseif ($dbtype === 'pgsql') { + return 'pgsql:host=' . $this->config['host'] . ';port=' . $this->config['port'] + . ($dbname !== null ? ';dbname=' . $dbname : ''); + } else { + throw new ConfigurationError( + 'Failed to build data source name. Unsupported PDO driver "%s"', + $dbtype + ); + } + } + + /** + * Try to connect to the server and throw an exception if this fails + * + * @throws PDOException In case an error occurs that does not indicate that authentication failed + */ + public function checkConnectivity() + { + try { + $this->connectToHost(); + } catch (PDOException $e) { + if ($this->config['db'] === 'mysql') { + $code = $e->getCode(); + if ($code !== 1040 && $code !== 1045) { + throw $e; + } + } elseif ($this->config['db'] === 'pgsql') { + if (strpos($e->getMessage(), $this->config['username']) === false) { + throw $e; + } + } + } + } +} From 929344e68b67788927984a4ee73c55b6c4eaeef8 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 12:25:29 +0200 Subject: [PATCH 025/318] Add container to store and handle installation requirements refs #7163 --- library/Icinga/Web/Setup/Requirements.php | 159 ++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 library/Icinga/Web/Setup/Requirements.php diff --git a/library/Icinga/Web/Setup/Requirements.php b/library/Icinga/Web/Setup/Requirements.php new file mode 100644 index 000000000..ce4642cbd --- /dev/null +++ b/library/Icinga/Web/Setup/Requirements.php @@ -0,0 +1,159 @@ +requirements[] = $requirement; + return $this; + } + + /** + * Return all registered requirements + * + * @return array + */ + public function getAll() + { + return $this->requirements; + } + + /** + * Return an iterator of all registered requirements + * + * @return ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->getAll()); + } + + /** + * Register an optional requirement + * + * @param string $title + * @param string $description + * @param bool $state + * @param string $message + * + * @return self + */ + public function addOptional($title, $description, $state, $message) + { + $this->add((object) array( + 'title' => $title, + 'message' => $message, + 'description' => $description, + 'state' => (bool) $state ? static::STATE_OK : static::STATE_OPTIONAL + )); + return $this; + } + + /** + * Register a mandatory requirement + * + * @param string $title + * @param string $description + * @param bool $state + * @param string $message + * + * @return self + */ + public function addMandatory($title, $description, $state, $message) + { + $this->add((object) array( + 'title' => $title, + 'message' => $message, + 'description' => $description, + 'state' => (bool) $state ? static::STATE_OK : static::STATE_MANDATORY + )); + return $this; + } + + /** + * Register the given requirements + * + * @param Requirements $requirements The requirements to register + * + * @return self + */ + public function merge(Requirements $requirements) + { + foreach ($requirements->getAll() as $requirement) { + $this->add($requirement); + } + + return $this; + } + + /** + * Make all registered requirements being optional + * + * @return self + */ + public function allOptional() + { + foreach ($this->getAll() as $requirement) { + if ($requirement->state === static::STATE_MANDATORY) { + $requirement->state = static::STATE_OPTIONAL; + } + } + + return $this; + } + + /** + * Return whether all mandatory requirements are fulfilled + * + * @return bool + */ + public function fulfilled() + { + foreach ($this->getAll() as $requirement) { + if ($requirement->state === static::STATE_MANDATORY) { + return false; + } + } + + return true; + } +} From 226575eddd0a82aa7a13a4bca3544877243441a5 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 12:26:27 +0200 Subject: [PATCH 026/318] Add welcome page refs #7163 --- application/forms/Setup/WelcomePage.php | 54 +++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 application/forms/Setup/WelcomePage.php diff --git a/application/forms/Setup/WelcomePage.php b/application/forms/Setup/WelcomePage.php new file mode 100644 index 000000000..1195fc651 --- /dev/null +++ b/application/forms/Setup/WelcomePage.php @@ -0,0 +1,54 @@ +setName('form_setup_welcome'); + } + + /** + * @see Form::createElements() + */ + public function createElements(array $formData) + { + $this->addElement( + 'note', + 'welcome', + array( + 'value' => t('%WELCOME%') + ) + ); + $this->addElement( + 'note', + 'description', + array( + 'value' => t('%DESCRIPTION%') + ) + ); + $this->addElement( + 'text', + 'token', + array( + 'required' => true, + 'label' => t('Setup Token'), + 'description' => t('Please enter the setup token you\'ve created earlier by using the icingacli'), + 'validators' => array(new TokenValidator(Icinga::app()->getConfigDir() . '/setup.token')) + ) + ); + } +} From a39c6a475a1117fd8bf26dbe873503a6a15b9db9 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 12:26:54 +0200 Subject: [PATCH 027/318] Add requirements page refs #7163 --- application/forms/Setup/RequirementsPage.php | 69 +++++++++++++++++++ .../scripts/form/setup-requirements.phtml | 33 +++++++++ 2 files changed, 102 insertions(+) create mode 100644 application/forms/Setup/RequirementsPage.php create mode 100644 application/views/scripts/form/setup-requirements.phtml diff --git a/application/forms/Setup/RequirementsPage.php b/application/forms/Setup/RequirementsPage.php new file mode 100644 index 000000000..ec629b03f --- /dev/null +++ b/application/forms/Setup/RequirementsPage.php @@ -0,0 +1,69 @@ +setName('setup_requirements'); + $this->setViewScript('form/setup-requirements.phtml'); + } + + /** + * Set the requirements to list + * + * @param Requirements $requirements + * + * @return self + */ + public function setRequirements(Requirements $requirements) + { + $this->requirements = $requirements; + return $this; + } + + /** + * Return the requirements to list + * + * @return Requirements + */ + public function getRequirements() + { + return $this->requirements; + } + + /** + * Validate the given form data and check whether the requirements are fulfilled + * + * @param array $data The data to validate + * + * @return bool + */ + public function isValid($data) + { + if (false === parent::isValid($data)) { + return false; + } + + return $this->requirements->fulfilled(); + } +} diff --git a/application/views/scripts/form/setup-requirements.phtml b/application/views/scripts/form/setup-requirements.phtml new file mode 100644 index 000000000..18fd2d1de --- /dev/null +++ b/application/views/scripts/form/setup-requirements.phtml @@ -0,0 +1,33 @@ +getRequirements(); + +?> + + + + + + + + + + +
title; ?>description; ?>message; ?>
+
+ getElement($form->getTokenElementName()); ?> + getElement($form->getUidElementName()); ?> + getElement(Wizard::BTN_PREV); ?> + getElement(Wizard::BTN_NEXT); + if (false === $requirements->fulfilled()) { + $btn->setAttrib('disabled', 1); + } + echo $btn; + ?> +
\ No newline at end of file From b1c32b182108afa4a78f7ccd6bc6c73bb05f09f3 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 12:27:11 +0200 Subject: [PATCH 028/318] Add authentication page refs #7163 --- .../forms/Setup/AuthenticationPage.php | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 application/forms/Setup/AuthenticationPage.php diff --git a/application/forms/Setup/AuthenticationPage.php b/application/forms/Setup/AuthenticationPage.php new file mode 100644 index 000000000..e5e2f1cfc --- /dev/null +++ b/application/forms/Setup/AuthenticationPage.php @@ -0,0 +1,61 @@ +setName('setup_authentication_type'); + } + + /** + * @see Form::createElements() + */ + public function createElements(array $formData) + { + $this->addElement( + 'note', + 'description', + array( + 'value' => t( + 'Please choose how you want to authenticate when accessing Icinga Web 2.' + . ' Configuring backend specific details follows in a later step.' + ) + ) + ); + + $backendTypes = array(); + if (Platform::extensionLoaded('pdo') && (Platform::zendClassExists('Zend_Db_Adapter_Pdo_Mysql') + || Platform::zendClassExists('Zend_Db_Adapter_Pdo_Pgsql'))) + { + $backendTypes['db'] = t('Database'); + } + if (Platform::extensionLoaded('ldap')) { + $backendTypes['ldap'] = 'LDAP'; + } + $backendTypes['autologin'] = t('Autologin'); + + $this->addElement( + 'select', + 'type', + array( + 'required' => true, + 'label' => t('Authentication Type'), + 'description' => t('The type of authentication to use when accessing Icinga Web 2'), + 'multiOptions' => $backendTypes + ) + ); + } +} From 61f0ce65e0e88ece58806eb7db5d1654bde1df83 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 12:27:26 +0200 Subject: [PATCH 029/318] Add preferences page refs #7163 --- application/forms/Setup/PreferencesPage.php | 70 +++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 application/forms/Setup/PreferencesPage.php diff --git a/application/forms/Setup/PreferencesPage.php b/application/forms/Setup/PreferencesPage.php new file mode 100644 index 000000000..f643d4a34 --- /dev/null +++ b/application/forms/Setup/PreferencesPage.php @@ -0,0 +1,70 @@ +setName('setup_preferences_type'); + } + + /** + * Pre-select "db" as preference backend and add a hint to the select element + * + * @return self + */ + public function showDatabaseNote() + { + $this->getElement('type') + ->setValue('db') + ->setDescription( + t('Note that choosing "Database" causes Icinga Web 2 to use the same database as for authentication.') + ); + return $this; + } + + /** + * @see Form::createElements() + */ + public function createElements(array $formData) + { + $this->addElement( + 'note', + 'description', + array( + 'value' => t('Please choose how Icinga Web 2 should store user preferences.') + ) + ); + + $storageTypes = array(); + $storageTypes['ini'] = t('File System (INI Files)'); + if (Platform::extensionLoaded('pdo') && (Platform::zendClassExists('Zend_Db_Adapter_Pdo_Mysql') + || Platform::zendClassExists('Zend_Db_Adapter_Pdo_Pgsql'))) + { + $storageTypes['db'] = t('Database'); + } + $storageTypes['null'] = t('Don\'t Store Preferences'); + + $this->addElement( + 'select', + 'type', + array( + 'required' => true, + 'label' => t('User Preference Storage Type'), + 'multiOptions' => $storageTypes + ) + ); + } +} From b782e790f414367f42229b38c12e679b0b59bc0c Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 12:27:43 +0200 Subject: [PATCH 030/318] Add DbResource page refs #7163 --- application/forms/Setup/DbResourcePage.php | 108 +++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 application/forms/Setup/DbResourcePage.php diff --git a/application/forms/Setup/DbResourcePage.php b/application/forms/Setup/DbResourcePage.php new file mode 100644 index 000000000..4847639ea --- /dev/null +++ b/application/forms/Setup/DbResourcePage.php @@ -0,0 +1,108 @@ +setName('setup_db_resource'); + } + + /** + * @see Form::createElements() + */ + public function createElements(array $formData) + { + $this->addElement( + 'hidden', + 'type', + array( + 'required' => true, + 'value' => 'db' + ) + ); + $this->addElement( + 'note', + 'description', + array( + 'value' => t( + 'Now please configure your database resource. Note that the database itself does not need' + . ' to exist at this time as it is going to be created when installing Icinga Web 2.' + ) + ) + ); + + if (isset($formData['skip_validation']) && $formData['skip_validation']) { + $this->addSkipValidationCheckbox(); + } else { + $this->addElement( + 'hidden', + 'skip_validation', + array( + 'required' => true, + 'value' => 0 + ) + ); + } + + $resourceForm = new DbResourceForm(); + $this->addElements($resourceForm->createElements($formData)->getElements()); + } + + /** + * Validate the given form data and check whether it's possible to connect to the database server + * + * @param array $data The data to validate + * + * @return bool + */ + public function isValid($data) + { + if (false === parent::isValid($data)) { + return false; + } + + if (false === isset($data['skip_validation']) || $data['skip_validation'] == 0) { + try { + $db = new DbTool($this->getValues()); + $db->checkConnectivity(); + } catch (PDOException $e) { + $this->addError($e->getMessage()); + $this->addSkipValidationCheckbox(); + return false; + } + } + + return true; + } + + /** + * Add a checkbox to the form by which the user can skip the connection validation + */ + protected function addSkipValidationCheckbox() + { + $this->addElement( + 'checkbox', + 'skip_validation', + array( + 'required' => true, + 'label' => t('Skip Validation'), + 'description' => t('Check this to not to validate connectivity with the given database server') + ) + ); + } +} From bd8f480cd0a13b628d07d49277e8859c13d7bd54 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 12:27:58 +0200 Subject: [PATCH 031/318] Add LdapResource page refs #7163 --- application/forms/Setup/LdapResourcePage.php | 102 +++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 application/forms/Setup/LdapResourcePage.php diff --git a/application/forms/Setup/LdapResourcePage.php b/application/forms/Setup/LdapResourcePage.php new file mode 100644 index 000000000..bee41acd6 --- /dev/null +++ b/application/forms/Setup/LdapResourcePage.php @@ -0,0 +1,102 @@ +setName('setup_ldap_resource'); + } + + /** + * @see Form::createElements() + */ + public function createElements(array $formData) + { + $this->addElement( + 'hidden', + 'type', + array( + 'required' => true, + 'value' => 'ldap' + ) + ); + $this->addElement( + 'note', + 'description', + array( + 'value' => t( + 'Now please configure your AD/LDAP resource. This will later ' + . 'be used to authenticate users logging in to Icinga Web 2.' + ) + ) + ); + + if (isset($formData['skip_validation']) && $formData['skip_validation']) { + $this->addSkipValidationCheckbox(); + } else { + $this->addElement( + 'hidden', + 'skip_validation', + array( + 'required' => true, + 'value' => 0 + ) + ); + } + + $resourceForm = new LdapResourceForm(); + $this->addElements($resourceForm->createElements($formData)->getElements()); + } + + /** + * Validate the given form data and check whether a BIND-request is successful + * + * @param array $data The data to validate + * + * @return bool + */ + public function isValid($data) + { + if (false === parent::isValid($data)) { + return false; + } + + if (false === isset($data['skip_validation']) || $data['skip_validation'] == 0) { + if (false === LdapResourceForm::isValidResource($this)) { + $this->addSkipValidationCheckbox(); + return false; + } + } + + return true; + } + + /** + * Add a checkbox to the form by which the user can skip the connection validation + */ + protected function addSkipValidationCheckbox() + { + $this->addElement( + 'checkbox', + 'skip_validation', + array( + 'required' => true, + 'label' => t('Skip Validation'), + 'description' => t('Check this to not to validate connectivity with the given directory service') + ) + ); + } +} From 5afda8a13e22d2f0829485019efa24d9a602adf7 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 12:28:12 +0200 Subject: [PATCH 032/318] Add AuthBackend page refs #7163 --- application/forms/Setup/AuthBackendPage.php | 135 ++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 application/forms/Setup/AuthBackendPage.php diff --git a/application/forms/Setup/AuthBackendPage.php b/application/forms/Setup/AuthBackendPage.php new file mode 100644 index 000000000..f6aaadceb --- /dev/null +++ b/application/forms/Setup/AuthBackendPage.php @@ -0,0 +1,135 @@ +setName('setup_authentication_backend'); + } + + /** + * Set the resource configuration to use + * + * @param array $config + * + * @return self + */ + public function setResourceConfig(array $config) + { + $this->config = $config; + return $this; + } + + /** + * Return the resource configuration as Zend_Config object + * + * @return Zend_Config + */ + public function getResourceConfig() + { + return new Zend_Config($this->config); + } + + /** + * @see Form::createElements() + */ + public function createElements(array $formData) + { + $this->addElement( + 'note', + 'description', + array( + 'value' => sprintf( + t( + 'Now please enter all configuration details required to authenticate using this %s backend.', + 'setup.auth.backend' + ), + $this->config['type'] === 'db' ? t('database', 'setup.auth.backend.type') : ( + $this->config['type'] === 'ldap' ? 'LDAP' : t('autologin', 'setup.auth.backend.type') + ) + ) + ) + ); + + if (isset($formData['skip_validation']) && $formData['skip_validation']) { + $this->addSkipValidationCheckbox(); + } + + if ($this->config['type'] === 'db') { + $backendForm = new DbBackendForm(); + $backendForm->createElements($formData)->removeElement('resource'); + } elseif ($this->config['type'] === 'ldap') { + $backendForm = new LdapBackendForm(); + $backendForm->createElements($formData)->removeElement('resource'); + } else { // $this->config['type'] === 'autologin' + $backendForm = new AutologinBackendForm(); + $backendForm->createElements($formData); + } + + $this->addElements($backendForm->getElements()); + } + + /** + * Validate the given form data and check whether it's possible to authenticate using the configured backend + * + * @param array $data The data to validate + * + * @return bool + */ + public function isValid($data) + { + if (false === parent::isValid($data)) { + return false; + } + + if (false === isset($data['skip_validation']) || $data['skip_validation'] == 0) { + if ($this->config['type'] === 'ldap' && false === LdapBackendForm::isValidAuthenticationBackend($this)) { + $this->addSkipValidationCheckbox(); + return false; + } + } + + return true; + } + + /** + * Add a checkbox to this form by which the user can skip the authentication validation + */ + protected function addSkipValidationCheckbox() + { + $this->addElement( + 'checkbox', + 'skip_validation', + array( + 'order' => 1, + 'ignore' => true, + 'required' => true, + 'label' => t('Skip Validation'), + 'description' => t('Check this to not to validate authentication using this backend') + ) + ); + } +} From b6b7814e8bc37e19b61f09529b7d9244d3530385 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 12:29:13 +0200 Subject: [PATCH 033/318] Add GeneralConfig page refs #7163 --- application/forms/Setup/GeneralConfigPage.php | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 application/forms/Setup/GeneralConfigPage.php diff --git a/application/forms/Setup/GeneralConfigPage.php b/application/forms/Setup/GeneralConfigPage.php new file mode 100644 index 000000000..d02a9b98f --- /dev/null +++ b/application/forms/Setup/GeneralConfigPage.php @@ -0,0 +1,35 @@ +setName('setup_application_config'); + } + + /** + * @see Form::createElements() + */ + public function createElements(array $formData) + { + $appForm = new ApplicationConfigForm(); + $this->addElement($appForm->createElements($formData)->getElement('global_modulePath')); + + $loggingForm = new LoggingConfigForm(); + $this->addElements($loggingForm->createElements($formData)->getElements()); + } +} From 243dd192810f647e0ec620a2aa87296a3966f262 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 12:29:36 +0200 Subject: [PATCH 034/318] Add AdminAccount page refs #7163 --- application/forms/Setup/AdminAccountPage.php | 242 ++++++++++++++++++ .../scripts/form/setup-admin-account.phtml | 65 +++++ 2 files changed, 307 insertions(+) create mode 100644 application/forms/Setup/AdminAccountPage.php create mode 100644 application/views/scripts/form/setup-admin-account.phtml diff --git a/application/forms/Setup/AdminAccountPage.php b/application/forms/Setup/AdminAccountPage.php new file mode 100644 index 000000000..b087a3cf7 --- /dev/null +++ b/application/forms/Setup/AdminAccountPage.php @@ -0,0 +1,242 @@ +setName('setup_admin_account'); + $this->setViewScript('form/setup-admin-account.phtml'); + } + + /** + * Set the resource configuration to use + * + * @param array $config + * + * @return self + */ + public function setResourceConfig(array $config) + { + $this->resourceConfig = $config; + return $this; + } + + /** + * Set the backend configuration to use + * + * @param array $config + * + * @return self + */ + public function setBackendConfig(array $config) + { + $this->backendConfig = $config; + return $this; + } + + /** + * @see Form::createElements() + */ + public function createElements(array $formData) + { + $choices = array(); + + if ($this->backendConfig['backend'] !== 'db') { + $choices['by_name'] = t('By Name', 'setup.admin'); + $this->addElement( + 'text', + 'by_name', + array( + 'required' => isset($formData['user_type']) && $formData['user_type'] === 'by_name', + 'value' => $this->getUsername(), + 'label' => t('Username'), + 'description' => t( + 'Define the initial administrative account by providing a username that reflects' + . ' a user created later or one that is authenticated using external mechanisms' + ) + ) + ); + } + + if ($this->backendConfig['backend'] === 'db' || $this->backendConfig['backend'] === 'ldap') { + $users = $this->fetchUsers(); + if (false === empty($users)) { + $choices['existing_user'] = t('Existing User'); + $this->addElement( + 'select', + 'existing_user', + array( + 'required' => isset($formData['user_type']) && $formData['user_type'] === 'existing_user', + 'label' => t('Username'), + 'description' => sprintf( + t( + 'Choose a user reported by the %s backend as the initial administrative account', + 'setup.admin' + ), + $this->backendConfig['backend'] === 'db' ? t('database', 'setup.admin.authbackend') : 'LDAP' + ), + 'multiOptions' => array_combine($users, $users) + ) + ); + } + } + + if ($this->backendConfig['backend'] === 'db') { + $choices['new_user'] = t('New User'); + $required = isset($formData['user_type']) && $formData['user_type'] === 'new_user'; + $this->addElement( + 'text', + 'new_user', + array( + 'required' => $required, + 'label' => t('Username'), + 'description' => t( + 'Enter the username to be used when creating an initial administrative account' + ) + ) + ); + $this->addElement( + 'password', + 'new_user_password', + array( + 'required' => $required, + 'label' => t('Password'), + 'description' => t('Enter the password to assign to the newly created account') + ) + ); + $this->addElement( + 'password', + 'new_user_2ndpass', + array( + 'required' => $required, + 'label' => t('Repeat password'), + 'description' => t('Please repeat the password given above to avoid typing errors'), + 'validators' => array( + array('identical', false, array('new_user_password')) + ) + ) + ); + } + + if (count($choices) > 1) { + $this->addElement( + 'radio', + 'user_type', + array( + 'required' => true, + 'multiOptions' => $choices + ) + ); + } else { + $this->addElement( + 'hidden', + 'user_type', + array( + 'required' => true, + 'value' => key($choices) + ) + ); + } + + $this->addElement( + 'note', + 'description', + array( + 'value' => tp( + 'Now it\'s time to configure your first administrative account.' + . ' Please follow the instructions below:', + 'Now it\'s time to configure your first administrative account.' + . ' Below are several options you can choose from. Select one and follow its instructions:', + count($choices) + ) + ) + ); + } + + /** + * Return the name of the externally authenticated user + * + * @return string + */ + protected function getUsername() + { + if (false === isset($_SERVER['REMOTE_USER'])) { + return ''; + } + + $name = $_SERVER['REMOTE_USER']; + if (isset($this->backendConfig['strip_username_regexp'])) { + // No need to silence or log anything here because the pattern has + // already been successfully compiled during backend configuration + $name = preg_replace($this->backendConfig['strip_username_regexp'], '', $name); + } + + return $name; + } + + /** + * Return the names of all users this backend currently provides + * + * @return array + * + * @throws LogicException In case the backend to fetch users from is not supported + */ + protected function fetchUsers() + { + if ($this->backendConfig['backend'] === 'db') { + $backend = new DbUserBackend(ResourceFactory::createResource(new Zend_Config($this->resourceConfig))); + } elseif ($this->backendConfig['backend'] === 'ldap') { + $backend = new LdapUserBackend( + ResourceFactory::createResource(new Zend_Config($this->resourceConfig)), + $this->backendConfig['user_class'], + $this->backendConfig['user_name_attribute'] + ); + } else { + throw new LogicException( + sprintf( + 'Tried to fetch users from an unsupported authentication backend: %s', + $this->backendConfig['backend'] + ) + ); + } + + try { + return $backend->listUsers(); + } catch (Exception $e) { + // No need to handle anything special here. Error means no users found. + return array(); + } + } +} diff --git a/application/views/scripts/form/setup-admin-account.phtml b/application/views/scripts/form/setup-admin-account.phtml new file mode 100644 index 000000000..dfd02b565 --- /dev/null +++ b/application/views/scripts/form/setup-admin-account.phtml @@ -0,0 +1,65 @@ +getElement('user_type'); +$showRadioBoxes = strpos(strtolower(get_class($radioElem)), 'radio') !== false; + +?> +
+ getElement('description'); ?> +getElement('by_name')) !== null): ?> +
+
+ +
+ +
+ +
+ +
+ +getElement('existing_user')) !== null): ?> +
+
+ +
+ +
+ +
+ +
+ +getElement('new_user')) !== null): ?> +
+
+ + getElement('new_user_password'); ?> + getElement('new_user_2ndpass'); ?> +
+ +
+ +
+ +
+ + + + + getElement($form->getTokenElementName()); ?> + getElement($form->getUidElementName()); ?> + getElement(Wizard::BTN_PREV); ?> + getElement(Wizard::BTN_NEXT); ?> +
\ No newline at end of file From 07d25e8bed6e245b33e96337bb88d6c92f93bd9d Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 12:56:36 +0200 Subject: [PATCH 035/318] Fix form tests --- .../Config/Authentication/DbBackendFormTest.php | 7 +++++-- .../Config/Authentication/LdapBackendFormTest.php | 7 ++++--- .../forms/Config/Resource/LdapResourceFormTest.php | 14 ++++++++++---- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/test/php/application/forms/Config/Authentication/DbBackendFormTest.php b/test/php/application/forms/Config/Authentication/DbBackendFormTest.php index 7474d2703..f11065294 100644 --- a/test/php/application/forms/Config/Authentication/DbBackendFormTest.php +++ b/test/php/application/forms/Config/Authentication/DbBackendFormTest.php @@ -9,6 +9,7 @@ namespace Tests\Icinga\Form\Config\Authentication; require_once realpath(dirname(__FILE__) . '/../../../../bootstrap.php'); use Mockery; +use Zend_Config; use Icinga\Test\BaseTestCase; use Icinga\Form\Config\Authentication\DbBackendForm; @@ -67,7 +68,9 @@ class DbBackendFormTest extends BaseTestCase protected function setUpResourceFactoryMock() { Mockery::mock('alias:Icinga\Data\ResourceFactory') - ->shouldReceive('create') - ->andReturn(Mockery::mock('Icinga\Data\Db\DbConnection')); + ->shouldReceive('createResource') + ->andReturn(Mockery::mock('Icinga\Data\Db\DbConnection')) + ->shouldReceive('getResourceConfig') + ->andReturn(new Zend_Config(array())); } } diff --git a/test/php/application/forms/Config/Authentication/LdapBackendFormTest.php b/test/php/application/forms/Config/Authentication/LdapBackendFormTest.php index c38f26376..2b8a90b4d 100644 --- a/test/php/application/forms/Config/Authentication/LdapBackendFormTest.php +++ b/test/php/application/forms/Config/Authentication/LdapBackendFormTest.php @@ -9,6 +9,7 @@ namespace Tests\Icinga\Form\Config\Authentication; require_once realpath(dirname(__FILE__) . '/../../../../bootstrap.php'); use Mockery; +use Zend_Config; use Icinga\Test\BaseTestCase; use Icinga\Form\Config\Authentication\LdapBackendForm; use Icinga\Exception\AuthenticationException; @@ -66,9 +67,9 @@ class LdapBackendFormTest extends BaseTestCase protected function setUpResourceFactoryMock() { Mockery::mock('alias:Icinga\Data\ResourceFactory') + ->shouldReceive('createResource') + ->andReturn(Mockery::mock('Icinga\Protocol\Ldap\Connection')) ->shouldReceive('getResourceConfig') - ->andReturn(new \Zend_Config(array())) - ->shouldReceive('create') - ->andReturn(Mockery::mock('Icinga\Protocol\Ldap\Connection')); + ->andReturn(new Zend_Config(array())); } } diff --git a/test/php/application/forms/Config/Resource/LdapResourceFormTest.php b/test/php/application/forms/Config/Resource/LdapResourceFormTest.php index b17a01a06..614f38f3a 100644 --- a/test/php/application/forms/Config/Resource/LdapResourceFormTest.php +++ b/test/php/application/forms/Config/Resource/LdapResourceFormTest.php @@ -27,11 +27,14 @@ class LdapResourceFormTest extends BaseTestCase public function testValidLdapResourceIsValid() { $this->setUpResourceFactoryMock( - Mockery::mock()->shouldReceive('connect')->getMock() + Mockery::mock()->shouldReceive('testCredentials')->once()->andReturn(true)->getMock() ); + $form = new LdapResourceForm(); + $form->setTokenDisabled(); + $this->assertTrue( - LdapResourceForm::isValidResource(new LdapResourceForm()), + LdapResourceForm::isValidResource($form->create()), 'ResourceForm claims that a valid ldap resource is not valid' ); } @@ -43,11 +46,14 @@ class LdapResourceFormTest extends BaseTestCase public function testInvalidLdapResourceIsNotValid() { $this->setUpResourceFactoryMock( - Mockery::mock()->shouldReceive('connect')->once()->andThrow('\Exception')->getMock() + Mockery::mock()->shouldReceive('testCredentials')->once()->andThrow('\Exception')->getMock() ); + $form = new LdapResourceForm(); + $form->setTokenDisabled(); + $this->assertFalse( - LdapResourceForm::isValidResource(new LdapResourceForm()), + LdapResourceForm::isValidResource($form->create()), 'ResourceForm claims that an invalid ldap resource is valid' ); } From 5d6391242c810137ca00e0b6eb6711ef5611b68c Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 14:18:50 +0200 Subject: [PATCH 036/318] Add interface for installers refs #7163 --- library/Icinga/Web/Setup/Installer.php | 32 ++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 library/Icinga/Web/Setup/Installer.php diff --git a/library/Icinga/Web/Setup/Installer.php b/library/Icinga/Web/Setup/Installer.php new file mode 100644 index 000000000..d61085278 --- /dev/null +++ b/library/Icinga/Web/Setup/Installer.php @@ -0,0 +1,32 @@ + Date: Mon, 29 Sep 2014 14:19:05 +0200 Subject: [PATCH 037/318] Add interface for setup wizards refs #7163 --- library/Icinga/Web/Setup/SetupWizard.php | 25 ++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 library/Icinga/Web/Setup/SetupWizard.php diff --git a/library/Icinga/Web/Setup/SetupWizard.php b/library/Icinga/Web/Setup/SetupWizard.php new file mode 100644 index 000000000..9d1a2ed3e --- /dev/null +++ b/library/Icinga/Web/Setup/SetupWizard.php @@ -0,0 +1,25 @@ + Date: Mon, 29 Sep 2014 14:23:42 +0200 Subject: [PATCH 038/318] Add web installer Logic is still missing and follows later. refs #7163 --- library/Icinga/Application/WebInstaller.php | 54 +++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 library/Icinga/Application/WebInstaller.php diff --git a/library/Icinga/Application/WebInstaller.php b/library/Icinga/Application/WebInstaller.php new file mode 100644 index 000000000..e83a6d7e0 --- /dev/null +++ b/library/Icinga/Application/WebInstaller.php @@ -0,0 +1,54 @@ +pageData = $pageData; + } + + /** + * @see Installer::run() + */ + public function run() + { + return true; + } + + /** + * @see Installer::getSummary() + */ + public function getSummary() + { + return array(); + } + + /** + * @see Installer::getReport() + */ + public function getReport() + { + return array(); + } +} From 28d16a89618463c951815101d372115adb5f17fd Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 14:30:34 +0200 Subject: [PATCH 039/318] Add web setup Page config and dependency handling is implemented and works like a charm. Though there is one known bug that occurs when navigating back, taking changes and moving onward to a page that needs to update its state due to the changes while adding some elements that have similar names as the ones shown before. This is only an issue with hidden elements. refs #7163 --- library/Icinga/Application/WebSetup.php | 254 ++++++++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100644 library/Icinga/Application/WebSetup.php diff --git a/library/Icinga/Application/WebSetup.php b/library/Icinga/Application/WebSetup.php new file mode 100644 index 000000000..34e764504 --- /dev/null +++ b/library/Icinga/Application/WebSetup.php @@ -0,0 +1,254 @@ +addPage(new WelcomePage()); + $this->addPage(new RequirementsPage()); + $this->addPage(new AuthenticationPage()); + $this->addPage(new PreferencesPage()); + $this->addPage(new DbResourcePage()); + $this->addPage(new LdapResourcePage()); + $this->addPage(new AuthBackendPage()); + $this->addPage(new GeneralConfigPage()); + $this->addPage(new AdminAccountPage()); + } + + /** + * @see Wizard::setupPage() + */ + public function setupPage(Form $page, Request $request) + { + if ($page->getName() === 'setup_requirements') { + $page->setRequirements($this->getRequirements()); + } elseif ($page->getName() === 'setup_preferences_type') { + $authData = $this->getPageData('setup_authentication_type'); + if ($authData['type'] === 'db') { + $page->create()->showDatabaseNote(); + } + } elseif ($page->getName() === 'setup_authentication_backend') { + $authData = $this->getPageData('setup_authentication_type'); + if ($authData['type'] === 'db') { + $page->setResourceConfig($this->getPageData('setup_db_resource')); + } elseif ($authData['type'] === 'ldap') { + $page->setResourceConfig($this->getPageData('setup_ldap_resource')); + } + } elseif ($page->getName() === 'setup_admin_account') { + $page->setBackendConfig($this->getPageData('setup_authentication_backend')); + $authData = $this->getPageData('setup_authentication_type'); + if ($authData['type'] === 'db') { + $page->setResourceConfig($this->getPageData('setup_db_resource')); + } elseif ($authData['type'] === 'ldap') { + $page->setResourceConfig($this->getPageData('setup_ldap_resource')); + } + } + } + + /** + * @see Wizard::getNewPage() + */ + protected function getNewPage($requestedPage, Form $originPage) + { + $newPage = parent::getNewPage($requestedPage, $originPage); + if ($newPage->getName() === 'setup_db_resource') { + $prefData = $this->getPageData('setup_preferences_type'); + $authData = $this->getPageData('setup_authentication_type'); + if ($prefData['type'] !== 'db' && $authData['type'] !== 'db') { + $pages = $this->getPages(); + if ($this->getDirection() === static::FORWARD) { + $nextPage = $pages[array_search($newPage, $pages, true) + 1]; + return $this->getNewPage($nextPage->getName(), $newPage); + } else { // $this->getDirection() === static::BACKWARD + $previousPage = $pages[array_search($newPage, $pages, true) - 1]; + return $this->getNewPage($previousPage->getName(), $newPage); + } + } + } elseif ($newPage->getName() === 'setup_ldap_resource') { + $authData = $this->getPageData('setup_authentication_type'); + if ($authData['type'] !== 'ldap') { + $pages = $this->getPages(); + if ($this->getDirection() === static::FORWARD) { + $nextPage = $pages[array_search($newPage, $pages, true) + 1]; + return $this->getNewPage($nextPage->getName(), $newPage); + } else { // $this->getDirection() === static::BACKWARD + $previousPage = $pages[array_search($newPage, $pages, true) - 1]; + return $this->getNewPage($previousPage->getName(), $newPage); + } + } + } + + return $newPage; + } + + /** + * @see SetupWizard::getInstaller() + */ + public function getInstaller() + { + return new WebInstaller($this->getPageData()); + } + + /** + * @see SetupWizard::getRequirements() + */ + public function getRequirements() + { + $requirements = new Requirements(); + + $phpVersion = Platform::getPhpVersion(); + $requirements->addMandatory( + t('PHP Version'), + t( + 'Running Icingaweb requires PHP version 5.3.2. Advanced features' + . ' like the built-in web server require PHP version 5.4.' + ), + version_compare($phpVersion, '5.3.2', '>='), + sprintf(t('You are running PHP version %s.'), $phpVersion) + ); + + $requirements->addOptional( + t('Linux Platform'), + t( + 'Icingaweb is developed for and tested on Linux. While we cannot' + . ' guarantee they will, other platforms may also perform as well.' + ), + Platform::isLinux(), + sprintf(t('You are running PHP on a %s system.'), Platform::getOperatingSystemName()) + ); + + $requirements->addOptional( + t('PHP Module: POSIX'), + t( + 'It is strongly suggested to install/enable the POSIX module for PHP. While ' . + 'it is not required for the web frontend it is essential for the Icinga CLI.' + ), + Platform::extensionLoaded('posix'), + Platform::extensionLoaded('posix') ? t('The PHP module POSIX is available.') : ( + t('The PHP module POSIX is missing.') + ) + ); + + $requirements->addOptional( + t('PHP Module: JSON'), + t('The JSON module for PHP is required for various export functionalities as well as APIs.'), + Platform::extensionLoaded('json'), + Platform::extensionLoaded('json') ? t('The PHP module JSON is available.') : ( + t('The PHP module JSON is missing.') + ) + ); + + $requirements->addOptional( + t('PHP Module: LDAP'), + t('If you\'d like to authenticate users using LDAP the corresponding PHP module is required'), + Platform::extensionLoaded('ldap'), + Platform::extensionLoaded('ldap') ? t('The PHP module LDAP is available') : ( + t('The PHP module LDAP is missing') + ) + ); + + $requirements->addOptional( + t('PHP Module: PDO'), + t( + 'Though Icingaweb can be operated without any database access, it is recommended to install/enable' . + ' the PDO module for PHP to gain a significant performance increase as well as more flexibility.' + ), + Platform::extensionLoaded('pdo'), + Platform::extensionLoaded('pdo') ? t('The PHP module PDO is available.') : ( + t('The PHP module PDO is missing.') + ) + ); + + $mysqlAdapterFound = Platform::zendClassExists('Zend_Db_Adapter_Pdo_Mysql'); + $requirements->addOptional( + t('Zend Database Adapter For MySQL'), + t('The Zend database adapter for MySQL is required to access a MySQL database.'), + $mysqlAdapterFound, + $mysqlAdapterFound ? t('The Zend database adapter for MySQL is available.') : ( + t('The Zend database adapter for MySQL is missing.') + ) + ); + + $pgsqlAdapterFound = Platform::zendClassExists('Zend_Db_Adapter_Pdo_Pgsql'); + $requirements->addOptional( + t('Zend Database Adapter For PostgreSQL'), + t('The Zend database adapter for PostgreSQL is required to access a PostgreSQL database.'), + $pgsqlAdapterFound, + $pgsqlAdapterFound ? t('The Zend database adapter for PostgreSQL is available.') : ( + t('The Zend database adapter for PostgreSQL is missing.') + ) + + ); + + $defaultTimezone = Platform::getPhpConfig('date.timezone'); + $requirements->addMandatory( + t('Default Timezone'), + t('It is required that a default timezone has been set using date.timezone in php.ini.'), + $defaultTimezone, + $defaultTimezone ? sprintf(t('Your default timezone is: %s'), $defaultTimezone) : ( + t('You did not define a default timezone.') + ) + ); + + $configDir = $this->getConfigDir(); + $requirements->addMandatory( + t('Writable Config Directory'), + t( + 'The Icingaweb configuration directory defaults to "/etc/icingaweb", if' . + ' not explicitly set in the environment variable "ICINGAWEB_CONFIGDIR".' + ), + is_writable($configDir), + sprintf( + is_writable($configDir) ? t('The current configuration directory is writable: %s') : ( + t('The current configuration directory is not writable: %s') + ), + $configDir + ) + ); + + return $requirements; + } + + /** + * Return the configuration directory of Icinga Web 2 + * + * @return string + */ + protected function getConfigDir() + { + if (array_key_exists('ICINGAWEB_CONFIGDIR', $_SERVER)) { + $configDir = $_SERVER['ICINGAWEB_CONFIGDIR']; + } else { + $configDir = '/etc/icingaweb'; + } + + $canonical = realpath($configDir); + return $canonical ? $canonical : $configDir; + } +} From 288ae6885bf161c61690309d7ede91497683ba0d Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 14:31:43 +0200 Subject: [PATCH 040/318] Show the web wizard, finally refs #7163 --- application/controllers/SetupController.php | 23 +++++++++++++++++-- .../setup/{index.phtml => install.phtml} | 0 application/views/scripts/setup/wizard.phtml | 1 + 3 files changed, 22 insertions(+), 2 deletions(-) rename application/views/scripts/setup/{index.phtml => install.phtml} (100%) create mode 100644 application/views/scripts/setup/wizard.phtml diff --git a/application/controllers/SetupController.php b/application/controllers/SetupController.php index fa8e3c68d..3e2174282 100644 --- a/application/controllers/SetupController.php +++ b/application/controllers/SetupController.php @@ -2,6 +2,7 @@ // {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}} +use Icinga\Application\WebSetup; use Icinga\Web\Controller\ActionController; class SetupController extends ActionController @@ -16,10 +17,28 @@ class SetupController extends ActionController protected $requiresAuthentication = false; /** - * Show the web wizard + * Show the web wizard and run the installation once finished */ public function indexAction() { - + $wizard = new WebSetup(); + + if ($wizard->isFinished()) { + $installer = $wizard->getInstaller(); + $success = $installer->run(); + if ($success) { + $wizard->getSession()->clear(); + } else { + $wizard->setIsFinished(false); + } + + $this->view->success = $success; + $this->view->report = $installer->getReport(); + $this->render('install'); + } else { + $wizard->handleRequest(); + $this->view->wizard = $wizard; + $this->render('wizard'); + } } } diff --git a/application/views/scripts/setup/index.phtml b/application/views/scripts/setup/install.phtml similarity index 100% rename from application/views/scripts/setup/index.phtml rename to application/views/scripts/setup/install.phtml diff --git a/application/views/scripts/setup/wizard.phtml b/application/views/scripts/setup/wizard.phtml new file mode 100644 index 000000000..94891f93e --- /dev/null +++ b/application/views/scripts/setup/wizard.phtml @@ -0,0 +1 @@ +getForm()->render(); ?> \ No newline at end of file From ab93969944b04ea588cdb2d818c72bd627219efe Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 14:46:27 +0200 Subject: [PATCH 041/318] Add missing platform checks to DbResourceForm --- .../forms/Config/Resource/DbResourceForm.php | 15 ++++++++++----- application/forms/Config/ResourceConfigForm.php | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/application/forms/Config/Resource/DbResourceForm.php b/application/forms/Config/Resource/DbResourceForm.php index 540a69813..6e361cb16 100644 --- a/application/forms/Config/Resource/DbResourceForm.php +++ b/application/forms/Config/Resource/DbResourceForm.php @@ -10,6 +10,7 @@ use Icinga\Web\Form; use Icinga\Web\Request; use Icinga\Web\Form\Element\Number; use Icinga\Data\ResourceFactory; +use Icinga\Application\Platform; /** * Form class for adding/modifying database resources @@ -29,6 +30,14 @@ class DbResourceForm extends Form */ public function createElements(array $formData) { + $dbChoices = array(); + if (Platform::zendClassExists('Zend_Db_Adapter_Pdo_Mysql')) { + $dbChoices['mysql'] = 'MySQL'; + } + if (Platform::zendClassExists('Zend_Db_Adapter_Pdo_Pgsql')) { + $dbChoices['pgsql'] = 'PostgreSQL'; + } + $this->addElement( 'text', 'name', @@ -45,11 +54,7 @@ class DbResourceForm extends Form 'required' => true, 'label' => t('Database Type'), 'description' => t('The type of SQL database'), - 'multiOptions' => array( - 'mysql' => 'MySQL', - 'pgsql' => 'PostgreSQL' - //'oracle' => 'Oracle' - ) + 'multiOptions' => $dbChoices ) ); $this->addElement( diff --git a/application/forms/Config/ResourceConfigForm.php b/application/forms/Config/ResourceConfigForm.php index cef9310be..9624b9e6a 100644 --- a/application/forms/Config/ResourceConfigForm.php +++ b/application/forms/Config/ResourceConfigForm.php @@ -220,7 +220,7 @@ class ResourceConfigForm extends ConfigForm if ($resourceType === 'ldap' || Platform::extensionLoaded('ldap')) { $resourceTypes['ldap'] = 'LDAP'; } - if ($resourceType === 'db' || Platform::extensionLoaded('mysql') || Platform::extensionLoaded('pgsql')) { + if ($resourceType === 'db' || Platform::extensionLoaded('pdo')) { $resourceTypes['db'] = t('SQL Database'); } From c3e3a4127d3a0c6aa87f060c6212a4856bc300fc Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 15:28:36 +0200 Subject: [PATCH 042/318] Add missing description to the GeneralConfigPage refs #7163 --- application/forms/Setup/GeneralConfigPage.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/application/forms/Setup/GeneralConfigPage.php b/application/forms/Setup/GeneralConfigPage.php index d02a9b98f..fa447ac86 100644 --- a/application/forms/Setup/GeneralConfigPage.php +++ b/application/forms/Setup/GeneralConfigPage.php @@ -26,6 +26,16 @@ class GeneralConfigPage extends Form */ public function createElements(array $formData) { + $this->addElement( + 'note', + 'description', + array( + 'value' => t( + 'Now please adjust all application and logging related configuration options to fit your needs.' + ) + ) + ); + $appForm = new ApplicationConfigForm(); $this->addElement($appForm->createElements($formData)->getElement('global_modulePath')); From e9e4ef01e5224750d71e89950a1eac8e694461a2 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 15:46:30 +0200 Subject: [PATCH 043/318] Use Icinga\Web\Form\Element\Note instead of Zend_Form_Element_Note Zend_Form_Element_Note is not available prior Zend v1.12.2 refs #7163 --- application/forms/Setup/AdminAccountPage.php | 20 ++++++++-------- application/forms/Setup/AuthBackendPage.php | 23 +++++++++++-------- .../forms/Setup/AuthenticationPage.php | 14 ++++++----- application/forms/Setup/DbResourcePage.php | 14 ++++++----- application/forms/Setup/GeneralConfigPage.php | 12 ++++++---- application/forms/Setup/LdapResourcePage.php | 14 ++++++----- application/forms/Setup/PreferencesPage.php | 10 ++++---- application/forms/Setup/WelcomePage.php | 19 ++++++++------- 8 files changed, 72 insertions(+), 54 deletions(-) diff --git a/application/forms/Setup/AdminAccountPage.php b/application/forms/Setup/AdminAccountPage.php index b087a3cf7..79237bac1 100644 --- a/application/forms/Setup/AdminAccountPage.php +++ b/application/forms/Setup/AdminAccountPage.php @@ -9,6 +9,7 @@ use Zend_Config; use LogicException; use Icinga\Web\Form; use Icinga\Data\ResourceFactory; +use Icinga\Web\Form\Element\Note; use Icinga\Authentication\Backend\DbUserBackend; use Icinga\Authentication\Backend\LdapUserBackend; @@ -171,15 +172,16 @@ class AdminAccountPage extends Form } $this->addElement( - 'note', - 'description', - array( - 'value' => tp( - 'Now it\'s time to configure your first administrative account.' - . ' Please follow the instructions below:', - 'Now it\'s time to configure your first administrative account.' - . ' Below are several options you can choose from. Select one and follow its instructions:', - count($choices) + new Note( + 'description', + array( + 'value' => tp( + 'Now it\'s time to configure your first administrative account.' + . ' Please follow the instructions below:', + 'Now it\'s time to configure your first administrative account.' + . ' Below are several options you can choose from. Select one and follow its instructions:', + count($choices) + ) ) ) ); diff --git a/application/forms/Setup/AuthBackendPage.php b/application/forms/Setup/AuthBackendPage.php index f6aaadceb..12d3e3f7d 100644 --- a/application/forms/Setup/AuthBackendPage.php +++ b/application/forms/Setup/AuthBackendPage.php @@ -6,6 +6,7 @@ namespace Icinga\Form\Setup; use Zend_Config; use Icinga\Web\Form; +use Icinga\Web\Form\Element\Note; use Icinga\Form\Config\Authentication\DbBackendForm; use Icinga\Form\Config\Authentication\LdapBackendForm; use Icinga\Form\Config\Authentication\AutologinBackendForm; @@ -59,16 +60,18 @@ class AuthBackendPage extends Form public function createElements(array $formData) { $this->addElement( - 'note', - 'description', - array( - 'value' => sprintf( - t( - 'Now please enter all configuration details required to authenticate using this %s backend.', - 'setup.auth.backend' - ), - $this->config['type'] === 'db' ? t('database', 'setup.auth.backend.type') : ( - $this->config['type'] === 'ldap' ? 'LDAP' : t('autologin', 'setup.auth.backend.type') + new Note( + 'description', + array( + 'value' => sprintf( + t( + 'Now please enter all configuration details required' + . ' to authenticate using this %s backend.', + 'setup.auth.backend' + ), + $this->config['type'] === 'db' ? t('database', 'setup.auth.backend.type') : ( + $this->config['type'] === 'ldap' ? 'LDAP' : t('autologin', 'setup.auth.backend.type') + ) ) ) ) diff --git a/application/forms/Setup/AuthenticationPage.php b/application/forms/Setup/AuthenticationPage.php index e5e2f1cfc..7ac565671 100644 --- a/application/forms/Setup/AuthenticationPage.php +++ b/application/forms/Setup/AuthenticationPage.php @@ -6,6 +6,7 @@ namespace Icinga\Form\Setup; use Icinga\Web\Form; use Icinga\Application\Platform; +use Icinga\Web\Form\Element\Note; /** * Wizard page to choose an authentication backend @@ -26,12 +27,13 @@ class AuthenticationPage extends Form public function createElements(array $formData) { $this->addElement( - 'note', - 'description', - array( - 'value' => t( - 'Please choose how you want to authenticate when accessing Icinga Web 2.' - . ' Configuring backend specific details follows in a later step.' + new Note( + 'description', + array( + 'value' => t( + 'Please choose how you want to authenticate when accessing Icinga Web 2.' + . ' Configuring backend specific details follows in a later step.' + ) ) ) ); diff --git a/application/forms/Setup/DbResourcePage.php b/application/forms/Setup/DbResourcePage.php index 4847639ea..37e08302b 100644 --- a/application/forms/Setup/DbResourcePage.php +++ b/application/forms/Setup/DbResourcePage.php @@ -7,6 +7,7 @@ namespace Icinga\Form\Setup; use PDOException; use Icinga\Web\Form; use Icinga\Web\Setup\DbTool; +use Icinga\Web\Form\Element\Note; use Icinga\Form\Config\Resource\DbResourceForm; /** @@ -36,12 +37,13 @@ class DbResourcePage extends Form ) ); $this->addElement( - 'note', - 'description', - array( - 'value' => t( - 'Now please configure your database resource. Note that the database itself does not need' - . ' to exist at this time as it is going to be created when installing Icinga Web 2.' + new Note( + 'description', + array( + 'value' => t( + 'Now please configure your database resource. Note that the database itself does not need' + . ' to exist at this time as it is going to be created when installing Icinga Web 2.' + ) ) ) ); diff --git a/application/forms/Setup/GeneralConfigPage.php b/application/forms/Setup/GeneralConfigPage.php index fa447ac86..689b393f1 100644 --- a/application/forms/Setup/GeneralConfigPage.php +++ b/application/forms/Setup/GeneralConfigPage.php @@ -5,6 +5,7 @@ namespace Icinga\Form\Setup; use Icinga\Web\Form; +use Icinga\Web\Form\Element\Note; use Icinga\Form\Config\General\LoggingConfigForm; use Icinga\Form\Config\General\ApplicationConfigForm; @@ -27,11 +28,12 @@ class GeneralConfigPage extends Form public function createElements(array $formData) { $this->addElement( - 'note', - 'description', - array( - 'value' => t( - 'Now please adjust all application and logging related configuration options to fit your needs.' + new Note( + 'description', + array( + 'value' => t( + 'Now please adjust all application and logging related configuration options to fit your needs.' + ) ) ) ); diff --git a/application/forms/Setup/LdapResourcePage.php b/application/forms/Setup/LdapResourcePage.php index bee41acd6..4a31502db 100644 --- a/application/forms/Setup/LdapResourcePage.php +++ b/application/forms/Setup/LdapResourcePage.php @@ -5,6 +5,7 @@ namespace Icinga\Form\Setup; use Icinga\Web\Form; +use Icinga\Web\Form\Element\Note; use Icinga\Form\Config\Resource\LdapResourceForm; /** @@ -34,12 +35,13 @@ class LdapResourcePage extends Form ) ); $this->addElement( - 'note', - 'description', - array( - 'value' => t( - 'Now please configure your AD/LDAP resource. This will later ' - . 'be used to authenticate users logging in to Icinga Web 2.' + new Note( + 'description', + array( + 'value' => t( + 'Now please configure your AD/LDAP resource. This will later ' + . 'be used to authenticate users logging in to Icinga Web 2.' + ) ) ) ); diff --git a/application/forms/Setup/PreferencesPage.php b/application/forms/Setup/PreferencesPage.php index f643d4a34..4e6a80cfb 100644 --- a/application/forms/Setup/PreferencesPage.php +++ b/application/forms/Setup/PreferencesPage.php @@ -6,6 +6,7 @@ namespace Icinga\Form\Setup; use Icinga\Web\Form; use Icinga\Application\Platform; +use Icinga\Web\Form\Element\Note; /** * Wizard page to choose a preference backend @@ -41,10 +42,11 @@ class PreferencesPage extends Form public function createElements(array $formData) { $this->addElement( - 'note', - 'description', - array( - 'value' => t('Please choose how Icinga Web 2 should store user preferences.') + new Note( + 'description', + array( + 'value' => t('Please choose how Icinga Web 2 should store user preferences.') + ) ) ); diff --git a/application/forms/Setup/WelcomePage.php b/application/forms/Setup/WelcomePage.php index 1195fc651..40c2fc564 100644 --- a/application/forms/Setup/WelcomePage.php +++ b/application/forms/Setup/WelcomePage.php @@ -6,6 +6,7 @@ namespace Icinga\Form\Setup; use Icinga\Application\Icinga; use Icinga\Web\Form; +use Icinga\Web\Form\Element\Note; use Icinga\Web\Form\Validator\TokenValidator; /** @@ -27,17 +28,19 @@ class WelcomePage extends Form public function createElements(array $formData) { $this->addElement( - 'note', - 'welcome', - array( - 'value' => t('%WELCOME%') + new Note( + 'welcome', + array( + 'value' => t('%WELCOME%') + ) ) ); $this->addElement( - 'note', - 'description', - array( - 'value' => t('%DESCRIPTION%') + new Note( + 'description', + array( + 'value' => t('%DESCRIPTION%') + ) ) ); $this->addElement( From 7b0152545649726f74aeafd860a769123fa88721 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 29 Sep 2014 16:42:38 +0200 Subject: [PATCH 044/318] Fix that navigation buttons have labels refs #7163 refs #7320 --- library/Icinga/Web/Wizard.php | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/library/Icinga/Web/Wizard.php b/library/Icinga/Web/Wizard.php index 3a69caf15..c60546c90 100644 --- a/library/Icinga/Web/Wizard.php +++ b/library/Icinga/Web/Wizard.php @@ -419,9 +419,10 @@ class Wizard 'button', static::BTN_NEXT, array( - 'type' => 'submit', - 'value' => $pages[1]->getName(), - 'label' => t('Next') + 'type' => 'submit', + 'value' => $pages[1]->getName(), + 'label' => t('Next'), + 'decorators' => array('ViewHelper') ) ); } elseif ($index < count($pages) - 1) { @@ -429,18 +430,20 @@ class Wizard 'button', static::BTN_PREV, array( - 'type' => 'submit', - 'value' => $pages[$index - 1]->getName(), - 'label' => t('Back') + 'type' => 'submit', + 'value' => $pages[$index - 1]->getName(), + 'label' => t('Back'), + 'decorators' => array('ViewHelper') ) ); $page->addElement( 'button', static::BTN_NEXT, array( - 'type' => 'submit', - 'value' => $pages[$index + 1]->getName(), - 'label' => t('Next') + 'type' => 'submit', + 'value' => $pages[$index + 1]->getName(), + 'label' => t('Next'), + 'decorators' => array('ViewHelper') ) ); } else { @@ -448,18 +451,20 @@ class Wizard 'button', static::BTN_PREV, array( - 'type' => 'submit', - 'value' => $pages[$index - 1]->getName(), - 'label' => t('Back') + 'type' => 'submit', + 'value' => $pages[$index - 1]->getName(), + 'label' => t('Back'), + 'decorators' => array('ViewHelper') ) ); $page->addElement( 'button', static::BTN_NEXT, array( - 'type' => 'submit', - 'value' => $page->getName(), - 'label' => t('Finish') + 'type' => 'submit', + 'value' => $page->getName(), + 'label' => t('Finish'), + 'decorators' => array('ViewHelper') ) ); } From c78b016d74eee0ce6c5e2943e0c15ef11855799e Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 30 Sep 2014 10:26:10 +0200 Subject: [PATCH 045/318] Remove boiler plate code --- library/Icinga/Application/WebSetup.php | 32 ++++++++++--------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/library/Icinga/Application/WebSetup.php b/library/Icinga/Application/WebSetup.php index 34e764504..f16a441f2 100644 --- a/library/Icinga/Application/WebSetup.php +++ b/library/Icinga/Application/WebSetup.php @@ -76,31 +76,25 @@ class WebSetup extends Wizard implements SetupWizard */ protected function getNewPage($requestedPage, Form $originPage) { + $skip = false; $newPage = parent::getNewPage($requestedPage, $originPage); if ($newPage->getName() === 'setup_db_resource') { $prefData = $this->getPageData('setup_preferences_type'); $authData = $this->getPageData('setup_authentication_type'); - if ($prefData['type'] !== 'db' && $authData['type'] !== 'db') { - $pages = $this->getPages(); - if ($this->getDirection() === static::FORWARD) { - $nextPage = $pages[array_search($newPage, $pages, true) + 1]; - return $this->getNewPage($nextPage->getName(), $newPage); - } else { // $this->getDirection() === static::BACKWARD - $previousPage = $pages[array_search($newPage, $pages, true) - 1]; - return $this->getNewPage($previousPage->getName(), $newPage); - } - } + $skip = $prefData['type'] !== 'db' && $authData['type'] !== 'db'; } elseif ($newPage->getName() === 'setup_ldap_resource') { $authData = $this->getPageData('setup_authentication_type'); - if ($authData['type'] !== 'ldap') { - $pages = $this->getPages(); - if ($this->getDirection() === static::FORWARD) { - $nextPage = $pages[array_search($newPage, $pages, true) + 1]; - return $this->getNewPage($nextPage->getName(), $newPage); - } else { // $this->getDirection() === static::BACKWARD - $previousPage = $pages[array_search($newPage, $pages, true) - 1]; - return $this->getNewPage($previousPage->getName(), $newPage); - } + $skip = $authData['type'] !== 'ldap'; + } + + if ($skip) { + $pages = $this->getPages(); + if ($this->getDirection() === static::FORWARD) { + $nextPage = $pages[array_search($newPage, $pages, true) + 1]; + $newPage = $this->getNewPage($nextPage->getName(), $newPage); + } else { // $this->getDirection() === static::BACKWARD + $previousPage = $pages[array_search($newPage, $pages, true) - 1]; + $newPage = $this->getNewPage($previousPage->getName(), $newPage); } } From 08d259eccfb027659af14f11cd9e618fa0b0066a Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 1 Oct 2014 09:16:53 +0200 Subject: [PATCH 046/318] Add database creation page refs #7163 --- .../forms/Setup/DatabaseCreationPage.php | 144 ++++++++++++++++++ library/Icinga/Application/WebSetup.php | 53 ++++++- library/Icinga/Web/Setup/DbTool.php | 103 ++++++++++++- 3 files changed, 295 insertions(+), 5 deletions(-) create mode 100644 application/forms/Setup/DatabaseCreationPage.php diff --git a/application/forms/Setup/DatabaseCreationPage.php b/application/forms/Setup/DatabaseCreationPage.php new file mode 100644 index 000000000..2c0d70cf8 --- /dev/null +++ b/application/forms/Setup/DatabaseCreationPage.php @@ -0,0 +1,144 @@ +setName('setup_database_creation'); + } + + /** + * Set the resource configuration to use + * + * @param array $config + * + * @return self + */ + public function setResourceConfig(array $config) + { + $this->config = $config; + return $this; + } + + /** + * Set the required database privileges + * + * @param array $privileges The required privileges + * + * @return self + */ + public function setDatabasePrivileges(array $privileges) + { + $this->databasePrivileges = $privileges; + return $this; + } + + /** + * @see Form::createElements() + */ + public function createElements(array $formData) + { + $this->addElement( + new Note( + 'description', + array( + 'value' => t( + 'It seems that either the database you defined earlier does not yet exist and cannot be created' + . ' using the provided access credentials or the database does not have the required schema to ' + . 'be operated by Icinga Web 2. Please provide appropriate access credentials to solve this.' + ) + ) + ) + ); + $this->addElement( + 'text', + 'username', + array( + 'required' => true, + 'label' => t('Username'), + 'description' => t('A user which is able to create databases and/or touch the database schema') + ) + ); + $this->addElement( + 'password', + 'password', + array( + 'required' => true, + 'label' => t('Password'), + 'description' => t('The password for the database user defined above') + ) + ); + } + + /** + * Validate the given form data and check whether the defined user has sufficient access rights + * + * @param array $data The data to validate + * + * @return bool + */ + public function isValid($data) + { + if (false === parent::isValid($data)) { + return false; + } + + $this->config['username'] = $this->getValue('username'); + $this->config['password'] = $this->getValue('password'); + $db = new DbTool($this->config); + + try { + $db->connectToDb(); + if (false === $db->checkPrivileges($this->databasePrivileges)) { + $this->addError( + t('The provided credentials do not have the required access rights to create the database schema.') + ); + return false; + } + } catch (PDOException $e) { + try { + $db->connectToHost(); + if (false === $db->checkPrivileges($this->databasePrivileges)) { + $this->addError( + t('The provided credentials cannot be used to create the database and/or the user.') + ); + return false; + } + } catch (PDOException $e) { + $this->addError($e->getMessage()); + return false; + } + } + + return true; + } +} diff --git a/library/Icinga/Application/WebSetup.php b/library/Icinga/Application/WebSetup.php index f16a441f2..bfdfe27cd 100644 --- a/library/Icinga/Application/WebSetup.php +++ b/library/Icinga/Application/WebSetup.php @@ -4,6 +4,7 @@ namespace Icinga\Application; +use PDOException; use Icinga\Form\Setup\WelcomePage; use Icinga\Form\Setup\DbResourcePage; use Icinga\Form\Setup\PreferencesPage; @@ -13,9 +14,11 @@ use Icinga\Form\Setup\LdapResourcePage; use Icinga\Form\Setup\RequirementsPage; use Icinga\Form\Setup\GeneralConfigPage; use Icinga\Form\Setup\AuthenticationPage; +use Icinga\Form\Setup\DatabaseCreationPage; use Icinga\Web\Form; use Icinga\Web\Wizard; use Icinga\Web\Request; +use Icinga\Web\Setup\DbTool; use Icinga\Web\Setup\SetupWizard; use Icinga\Web\Setup\Requirements; use Icinga\Application\Platform; @@ -25,6 +28,31 @@ use Icinga\Application\Platform; */ class WebSetup extends Wizard implements SetupWizard { + /** + * The database tables required by Icinga Web 2 + * + * @var array + */ + protected $databaseTables = array('account', 'preference'); + + /** + * The privileges required by Icinga Web 2 to setup the database + * + * @var array + */ + protected $databaseSetupPrivileges = array( + 'USAGE', + 'CREATE', + 'ALTER', + 'INSERT', + 'UPDATE', + 'DELETE', + 'TRUNCATE', + 'REFERENCES', + 'CREATE USER', + 'GRANT OPTION' + ); + /** * @see Wizard::init() */ @@ -37,8 +65,9 @@ class WebSetup extends Wizard implements SetupWizard $this->addPage(new DbResourcePage()); $this->addPage(new LdapResourcePage()); $this->addPage(new AuthBackendPage()); - $this->addPage(new GeneralConfigPage()); $this->addPage(new AdminAccountPage()); + $this->addPage(new GeneralConfigPage()); + $this->addPage(new DatabaseCreationPage()); } /** @@ -68,6 +97,9 @@ class WebSetup extends Wizard implements SetupWizard } elseif ($authData['type'] === 'ldap') { $page->setResourceConfig($this->getPageData('setup_ldap_resource')); } + } elseif ($page->getName() === 'setup_database_creation') { + $page->setDatabasePrivileges($this->databaseSetupPrivileges); + $page->setResourceConfig($this->getPageData('setup_db_resource')); } } @@ -85,6 +117,25 @@ class WebSetup extends Wizard implements SetupWizard } elseif ($newPage->getName() === 'setup_ldap_resource') { $authData = $this->getPageData('setup_authentication_type'); $skip = $authData['type'] !== 'ldap'; + } elseif ($newPage->getName() === 'setup_database_creation') { + if ($this->hasPageData('setup_db_resource')) { + $db = new DbTool($this->getPageData('setup_db_resource')); + + try { + $db->connectToDb(); + $diff = array_diff($this->databaseTables, $db->listTables()); + if (false === empty($diff)) { + $skip = $db->checkPrivileges($this->databaseSetupPrivileges); + } else { + $skip = true; + } + } catch (PDOException $e) { + $db->connectToHost(); + $skip = $db->checkPrivileges($this->databaseSetupPrivileges); + } + } else { + $skip = true; + } } if ($skip) { diff --git a/library/Icinga/Web/Setup/DbTool.php b/library/Icinga/Web/Setup/DbTool.php index 895065540..a3ded9707 100644 --- a/library/Icinga/Web/Setup/DbTool.php +++ b/library/Icinga/Web/Setup/DbTool.php @@ -6,6 +6,9 @@ namespace Icinga\Web\Setup; use PDO; use PDOException; +use LogicException; +use Zend_Db_Adapter_Pdo_Mysql; +use Zend_Db_Adapter_Pdo_Pgsql; use Icinga\Exception\ConfigurationError; /** @@ -14,11 +17,18 @@ use Icinga\Exception\ConfigurationError; class DbTool { /** - * The database connection + * The PDO database connection * * @var PDO */ - protected $conn; + protected $pdoConn; + + /** + * The Zend database adapter + * + * @var Zend_Db_Adapter_Pdo_Abstract + */ + protected $zendConn; /** * The resource configuration @@ -39,21 +49,27 @@ class DbTool /** * Connect to the server + * + * @return self */ public function connectToHost() { $this->assertHostAccess(); $this->connect(); + return $this; } /** * Connect to the database + * + * @return self */ public function connectToDb() { $this->assertHostAccess(); $this->assertDatabaseAccess(); $this->connect($this->config['dbname']); + return $this; } /** @@ -88,6 +104,18 @@ class DbTool } } + /** + * Assert that a connection with a database has been established + * + * @throws LogicException + */ + protected function assertConnectedToDb() + { + if ($this->zendConn === null) { + throw new LogicException('Not connected to database'); + } + } + /** * Establish a connection with the database or just the server by omitting the database name * @@ -95,11 +123,55 @@ class DbTool */ public function connect($dbname = null) { - if ($this->conn !== null) { + $this->_pdoConnect($dbname); + if ($dbname !== null) { + $this->_zendConnect($dbname); + } + } + + /** + * Initialize Zend database adapter + * + * @param string $dbname The name of the database to connect with + * + * @throws ConfigurationError In case the resource type is not a supported PDO driver name + */ + protected function _zendConnect($dbname) + { + if ($this->zendConn !== null) { return; } - $this->conn = new PDO( + $config = array( + 'dbname' => $dbname, + 'username' => $this->config['username'], + 'password' => $this->config['password'] + ); + + if ($this->config['db'] === 'mysql') { + $this->zendConn = new Zend_Db_Adapter_Pdo_Mysql($config); + } elseif ($this->config['db'] === 'pgsql') { + $this->zendConn = new Zend_Db_Adapter_Pdo_Pgsql($config); + } else { + throw new ConfigurationError( + 'Failed to connect to database. Unsupported PDO driver "%s"', + $this->config['db'] + ); + } + } + + /** + * Initialize PDO connection + * + * @param string $dbname The name of the database to connect with + */ + protected function _pdoConnect($dbname) + { + if ($this->pdoConn !== null) { + return; + } + + $this->pdoConn = new PDO( $this->buildDsn($this->config['db'], $dbname), $this->config['username'], $this->config['password'], @@ -155,4 +227,27 @@ class DbTool } } } + + /** + * Return whether the given privileges were granted + * + * @param array $privileges An array of strings with the required privilege names + * + * @return bool + */ + public function checkPrivileges(array $privileges) + { + return true; // TODO(7163): Implement privilege checks + } + + /** + * Return a list of all existing database tables + * + * @return array + */ + public function listTables() + { + $this->assertConnectedToDb(); + return $this->zendConn->listTables(); + } } From 8f79e0a7132acfc43bd55022012866656268d53e Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 1 Oct 2014 09:17:48 +0200 Subject: [PATCH 047/318] Fix warning "references should be passed by variable only" --- library/Icinga/Web/Wizard.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Web/Wizard.php b/library/Icinga/Web/Wizard.php index c60546c90..d64558d9b 100644 --- a/library/Icinga/Web/Wizard.php +++ b/library/Icinga/Web/Wizard.php @@ -326,7 +326,8 @@ class Wizard */ protected function isLastPage(Form $page) { - return $page->getName() === end($this->getPages())->getName(); + $pages = $this->getPages(); + return $page->getName() === end($pages)->getName(); } /** From e186c21821b36dac6ca17a0446e57234c1427743 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 1 Oct 2014 09:18:44 +0200 Subject: [PATCH 048/318] Fix form submit when JS is enabled refs #7163 --- application/views/scripts/setup/wizard.phtml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/application/views/scripts/setup/wizard.phtml b/application/views/scripts/setup/wizard.phtml index 94891f93e..61f48e029 100644 --- a/application/views/scripts/setup/wizard.phtml +++ b/application/views/scripts/setup/wizard.phtml @@ -1 +1,3 @@ -getForm()->render(); ?> \ No newline at end of file +
+ getForm()->render(); ?> +
\ No newline at end of file From 4ef0a5a74a907c640934381cc9323c5094a87952 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 1 Oct 2014 10:01:25 +0200 Subject: [PATCH 049/318] Fix wizard page css refs #7163 --- public/css/icinga/forms.less | 6 +++--- public/css/icinga/login.less | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/css/icinga/forms.less b/public/css/icinga/forms.less index b201df7c6..7d9840ace 100644 --- a/public/css/icinga/forms.less +++ b/public/css/icinga/forms.less @@ -125,18 +125,18 @@ form ul.errors li { line-height: 1.5em; } -#main form label { +form label { display: inline-block; margin-right: 1em; font-size: 0.9em; width: 10em; } -#main select, #main input[type=text] { +select, input[type=text] { width: 20em; } -#main form .help-block, #main form .description { +form .description { font-size: 0.8em; } diff --git a/public/css/icinga/login.less b/public/css/icinga/login.less index 9f22fa60d..93e367382 100644 --- a/public/css/icinga/login.less +++ b/public/css/icinga/login.less @@ -83,7 +83,7 @@ color: #eee; border-color: #777; background: #777; - margin-left: 11em; + margin-left: 10.1em; } input[type=submit]:hover, a.button:hover, input[type=submit]:focus { From 3a11182bf071540a740368bb19f2cefe8dfe8904 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 1 Oct 2014 10:33:06 +0200 Subject: [PATCH 050/318] Fix welcome page name not conforming the "standard" refs #7163 --- application/forms/Setup/WelcomePage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/forms/Setup/WelcomePage.php b/application/forms/Setup/WelcomePage.php index 40c2fc564..83007ba26 100644 --- a/application/forms/Setup/WelcomePage.php +++ b/application/forms/Setup/WelcomePage.php @@ -19,7 +19,7 @@ class WelcomePage extends Form */ public function init() { - $this->setName('form_setup_welcome'); + $this->setName('setup_welcome'); } /** From 464fefa57899d105e4aa6672f69c969275a07815 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 1 Oct 2014 15:44:43 +0200 Subject: [PATCH 051/318] Fix exception in case the database access credentials are invalid refs #7163 --- library/Icinga/Application/WebSetup.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/library/Icinga/Application/WebSetup.php b/library/Icinga/Application/WebSetup.php index bfdfe27cd..82a9c59c9 100644 --- a/library/Icinga/Application/WebSetup.php +++ b/library/Icinga/Application/WebSetup.php @@ -130,8 +130,12 @@ class WebSetup extends Wizard implements SetupWizard $skip = true; } } catch (PDOException $e) { - $db->connectToHost(); - $skip = $db->checkPrivileges($this->databaseSetupPrivileges); + try { + $db->connectToHost(); + $skip = $db->checkPrivileges($this->databaseSetupPrivileges); + } catch (PDOException $e) { + // skip should already be false, nothing to do + } } } else { $skip = true; From a980184eb906f24b5db1c921f0dd6dc51cd06a8d Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 1 Oct 2014 15:46:58 +0200 Subject: [PATCH 052/318] Move css instructions to dedicated stylesheet refs #7163 --- .../scripts/form/setup-admin-account.phtml | 12 ++++---- .../scripts/form/setup-requirements.phtml | 6 ++-- application/views/scripts/setup/wizard.phtml | 2 +- library/Icinga/Web/StyleSheet.php | 1 + public/css/icinga/wizard.less | 28 +++++++++++++++++++ 5 files changed, 39 insertions(+), 10 deletions(-) create mode 100644 public/css/icinga/wizard.less diff --git a/application/views/scripts/form/setup-admin-account.phtml b/application/views/scripts/form/setup-admin-account.phtml index dfd02b565..93719dc97 100644 --- a/application/views/scripts/form/setup-admin-account.phtml +++ b/application/views/scripts/form/setup-admin-account.phtml @@ -10,11 +10,11 @@ $showRadioBoxes = strpos(strtolower(get_class($radioElem)), 'radio') !== false; getElement('description'); ?> getElement('by_name')) !== null): ?>
-
+
-
+