From 3e02d5aff85babc3ffbfcf52313f2108e313aa23 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 13 Apr 2024 13:46:43 +0200 Subject: Adding upstream version 2.12.1. Signed-off-by: Daniel Baumann --- .../application/clicommands/ConfigCommand.php | 188 +++++++++ .../setup/application/clicommands/TokenCommand.php | 89 +++++ .../application/controllers/IndexController.php | 91 +++++ .../setup/application/forms/AdminAccountPage.php | 431 +++++++++++++++++++++ .../setup/application/forms/AuthBackendPage.php | 274 +++++++++++++ .../setup/application/forms/AuthenticationPage.php | 69 ++++ .../application/forms/DatabaseCreationPage.php | 209 ++++++++++ modules/setup/application/forms/DbResourcePage.php | 183 +++++++++ .../setup/application/forms/GeneralConfigPage.php | 41 ++ .../application/forms/LdapDiscoveryConfirmPage.php | 133 +++++++ .../setup/application/forms/LdapDiscoveryPage.php | 115 ++++++ .../setup/application/forms/LdapResourcePage.php | 152 ++++++++ modules/setup/application/forms/ModulePage.php | 108 ++++++ .../setup/application/forms/RequirementsPage.php | 68 ++++ modules/setup/application/forms/SummaryPage.php | 84 ++++ .../application/forms/UserGroupBackendPage.php | 147 +++++++ modules/setup/application/forms/WelcomePage.php | 45 +++ .../views/scripts/form/setup-modules.phtml | 33 ++ .../views/scripts/form/setup-requirements.phtml | 48 +++ .../views/scripts/form/setup-summary.phtml | 40 ++ .../views/scripts/form/setup-welcome.phtml | 120 ++++++ .../application/views/scripts/index/index.phtml | 224 +++++++++++ .../views/scripts/index/parts/finish.phtml | 34 ++ .../views/scripts/index/parts/wizard.phtml | 1 + 24 files changed, 2927 insertions(+) create mode 100644 modules/setup/application/clicommands/ConfigCommand.php create mode 100644 modules/setup/application/clicommands/TokenCommand.php create mode 100644 modules/setup/application/controllers/IndexController.php create mode 100644 modules/setup/application/forms/AdminAccountPage.php create mode 100644 modules/setup/application/forms/AuthBackendPage.php create mode 100644 modules/setup/application/forms/AuthenticationPage.php create mode 100644 modules/setup/application/forms/DatabaseCreationPage.php create mode 100644 modules/setup/application/forms/DbResourcePage.php create mode 100644 modules/setup/application/forms/GeneralConfigPage.php create mode 100644 modules/setup/application/forms/LdapDiscoveryConfirmPage.php create mode 100644 modules/setup/application/forms/LdapDiscoveryPage.php create mode 100644 modules/setup/application/forms/LdapResourcePage.php create mode 100644 modules/setup/application/forms/ModulePage.php create mode 100644 modules/setup/application/forms/RequirementsPage.php create mode 100644 modules/setup/application/forms/SummaryPage.php create mode 100644 modules/setup/application/forms/UserGroupBackendPage.php create mode 100644 modules/setup/application/forms/WelcomePage.php create mode 100644 modules/setup/application/views/scripts/form/setup-modules.phtml create mode 100644 modules/setup/application/views/scripts/form/setup-requirements.phtml create mode 100644 modules/setup/application/views/scripts/form/setup-summary.phtml create mode 100644 modules/setup/application/views/scripts/form/setup-welcome.phtml create mode 100644 modules/setup/application/views/scripts/index/index.phtml create mode 100644 modules/setup/application/views/scripts/index/parts/finish.phtml create mode 100644 modules/setup/application/views/scripts/index/parts/wizard.phtml (limited to 'modules/setup/application') diff --git a/modules/setup/application/clicommands/ConfigCommand.php b/modules/setup/application/clicommands/ConfigCommand.php new file mode 100644 index 0000000..e50333e --- /dev/null +++ b/modules/setup/application/clicommands/ConfigCommand.php @@ -0,0 +1,188 @@ + Path to Icinga Web 2's configuration files [/etc/icingaweb2] + * + * --mode= The access mode to use [2770] + * + * --group= Owner group for the configuration directory [icingaweb2] + * + * EXAMPLES: + * + * icingacli setup config directory + * + * icingacli setup config directory --mode=2775 --config=/opt/icingaweb2/etc + */ + public function directoryAction() + { + $configDir = trim($this->params->get('config', $this->app->getConfigDir())); + if (strlen($configDir) === 0) { + $this->fail($this->translate( + 'The argument --config expects a path to Icinga Web 2\'s configuration files' + )); + } + + $group = trim($this->params->get('group', 'icingaweb2')); + if (strlen($group) === 0) { + $this->fail($this->translate( + 'The argument --group expects a owner group for the configuration directory' + )); + } + + $mode = trim($this->params->get('mode', '2770')); + if (strlen($mode) === 0) { + $this->fail($this->translate( + 'The argument --mode expects an access mode for the configuration directory' + )); + } + + if (! file_exists($configDir) && ! @mkdir($configDir, 0755, true)) { + $e = error_get_last(); + $this->fail(sprintf( + $this->translate('Can\'t create configuration directory %s: %s'), + $configDir, + $e['message'] + )); + } + + if (! @chmod($configDir, octdec($mode))) { + $e = error_get_last(); + $this->fail(sprintf( + $this->translate('Can\'t change the mode of the configuration directory to %s: %s'), + $mode, + $e['message'] + )); + } + + if (! @chgrp($configDir, $group)) { + $e = error_get_last(); + $this->fail(sprintf( + $this->translate('Can\'t change the group of %s to %s: %s'), + $configDir, + $group, + $e['message'] + )); + } + + printf($this->translate('Successfully created configuration directory %s') . PHP_EOL, $configDir); + } + + /** + * Create webserver configuration + * + * USAGE: + * + * icingacli setup config webserver [options] + * + * OPTIONS: + * + * --path= The URL path to Icinga Web 2 [/icingaweb2] + * + * --root|--document-root= The directory from which the webserver will serve files + * [/path/to/icingaweb2/public] + * + * --enable-fpm Enable FPM handler for Apache (Nginx is always enabled) + * + * --fpm-uri= Address or path where to pass requests to FPM [127.0.0.1:9000] + * + * --config= Path to Icinga Web 2's configuration files [/etc/icingaweb2] + * + * --file= Write configuration to file [stdout] + * + * EXAMPLES: + * + * icingacli setup config webserver apache + * + * icingacli setup config webserver apache \ + * --path=/icingaweb2 \ + * --document-root=/usr/share/icingaweb2/public \ + * --config=/etc/icingaweb2 + * + * icingacli setup config webserver apache \ + * --file=/etc/apache2/conf.d/icingaweb2.conf + * + * icingacli setup config webserver nginx \ + * --root=/usr/share/icingaweb2/public \ + * --fpm-uri=unix:/var/run/php5-fpm.sock + */ + public function webserverAction() + { + if (($type = $this->params->getStandalone()) === null) { + $this->fail($this->translate('Argument type is mandatory.')); + } + + $webserver = null; + try { + $webserver = Webserver::createInstance($type); + } catch (ProgrammingError $e) { + $this->fail($this->translate('Unknown type') . ': ' . $type); + } + $urlPath = trim($this->params->get('path', $webserver->getUrlPath())); + if (strlen($urlPath) === 0) { + $this->fail($this->translate('The argument --path expects a URL path')); + } + $documentRoot = trim( + $this->params->get('root', $this->params->get('document-root', $webserver->getDocumentRoot())) + ); + if (strlen($documentRoot) === 0) { + $this->fail($this->translate( + 'The argument --root/--document-root expects a directory from which the webserver will serve files' + )); + } + $configDir = trim($this->params->get('config', $webserver->getConfigDir())); + if (strlen($configDir) === 0) { + $this->fail($this->translate( + 'The argument --config expects a path to Icinga Web 2\'s configuration files' + )); + } + + $enableFpm = $this->params->shift('enable-fpm', $webserver->getEnableFpm()); + + $fpmUri = trim($this->params->get('fpm-uri', $webserver->getFpmUri())); + if (empty($fpmUri)) { + $this->fail($this->translate( + 'The argument --fpm-uri expects an address or path where to pass requests to FPM' + )); + } + $webserver + ->setDocumentRoot($documentRoot) + ->setConfigDir($configDir) + ->setUrlPath($urlPath) + ->setEnableFpm($enableFpm) + ->setFpmUri($fpmUri); + $config = $webserver->generate() . "\n"; + if (($file = $this->params->get('file')) !== null) { + if (file_exists($file) === true) { + $this->fail(sprintf($this->translate('File %s already exists. Please delete it first.'), $file)); + } + Logger::info($this->translate('Write %s configuration to file: %s'), $type, $file); + $re = file_put_contents($file, $config); + if ($re === false) { + $this->fail($this->translate('Could not write to file') . ': ' . $file); + } + Logger::info($this->translate('Successfully written %d bytes to file'), $re); + return true; + } + echo $config; + return true; + } +} diff --git a/modules/setup/application/clicommands/TokenCommand.php b/modules/setup/application/clicommands/TokenCommand.php new file mode 100644 index 0000000..f1c30d1 --- /dev/null +++ b/modules/setup/application/clicommands/TokenCommand.php @@ -0,0 +1,89 @@ + + */ +class TokenCommand extends Command +{ + /** + * Display the current setup token + * + * Shows you the current setup token used to authenticate when setting up Icinga Web 2 using the web-based wizard. + * + * USAGE: + * + * icingacli setup token show [options] + * + * OPTIONS: + * + * --config= Path to Icinga Web 2's configuration files [/etc/icingaweb2] + */ + public function showAction() + { + $configDir = $this->params->get('config', $this->app->getConfigDir()); + if (! is_string($configDir) || strlen(trim($configDir)) === 0) { + $this->fail($this->translate( + 'The argument --config expects a path to Icinga Web 2\'s configuration files' + )); + } + + $token = file_get_contents($configDir . '/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 setting up Icinga Web 2 using the web-based wizard. + * + * USAGE: + * + * icingacli setup token create [options] + * + * OPTIONS: + * + * --config= Path to Icinga Web 2's configuration files [/etc/icingaweb2] + */ + public function createAction() + { + $configDir = $this->params->get('config', $this->app->getConfigDir()); + if (! is_string($configDir) || strlen(trim($configDir)) === 0) { + $this->fail($this->translate( + 'The argument --config expects a path to Icinga Web 2\'s configuration files' + )); + } + + $file = $configDir . '/setup.token'; + + if (function_exists('openssl_random_pseudo_bytes')) { + $token = bin2hex(openssl_random_pseudo_bytes(8)); + } else { + $token = substr(md5(mt_rand()), 16); + } + + if (false === file_put_contents($file, $token)) { + $this->fail(sprintf($this->translate('Cannot write setup token "%s" to disk.'), $file)); + } + + if (! chmod($file, 0660)) { + $this->fail(sprintf($this->translate('Cannot change access mode of "%s" to %o.'), $file, 0660)); + } + + printf($this->translate("The newly generated setup token is: %s\n"), $token); + } +} diff --git a/modules/setup/application/controllers/IndexController.php b/modules/setup/application/controllers/IndexController.php new file mode 100644 index 0000000..b75643c --- /dev/null +++ b/modules/setup/application/controllers/IndexController.php @@ -0,0 +1,91 @@ +isFinished()) { + $setup = $wizard->getSetup(); + $success = $setup->run(); + if ($success) { + $wizard->clearSession(); + } else { + $wizard->setIsFinished(false); + } + + $this->view->success = $success; + $this->view->report = $setup->getReport(); + } else { + $wizard->handleRequest(); + + $restartForm = new Form(); + $restartForm->setUidDisabled(); + $restartForm->setName('setup_restart_form'); + $restartForm->setAction(Url::fromPath('setup/index/restart')); + $restartForm->setAttrib('class', 'restart-form'); + $restartForm->addElement( + 'button', + 'btn_submit', + array( + 'type' => 'submit', + 'value' => 'btn_submit', + 'escape' => false, + 'label' => $this->view->icon('reply-all'), + 'title' => $this->translate('Restart the setup'), + 'decorators' => array('ViewHelper') + ) + ); + + $this->view->restartForm = $restartForm; + } + + $this->view->wizard = $wizard; + $this->view->title = $this->translate('Setup') . ' :: ' . $this->view->defaultTitle; + } + + /** + * Reset session and restart the wizard + */ + public function restartAction() + { + $this->assertHttpMethod('POST'); + + $form = new Form(array( + 'onSuccess' => function () { + $wizard = new WebWizard(); + $wizard->clearSession(false); + } + )); + $form->setUidDisabled(); + $form->setRedirectUrl('setup'); + $form->setSubmitLabel('btn_submit'); + $form->handleRequest(); + } +} diff --git a/modules/setup/application/forms/AdminAccountPage.php b/modules/setup/application/forms/AdminAccountPage.php new file mode 100644 index 0000000..b33749e --- /dev/null +++ b/modules/setup/application/forms/AdminAccountPage.php @@ -0,0 +1,431 @@ +setName('setup_admin_account'); + $this->setTitle($this->translate('Administration', 'setup.page.title')); + $this->addDescription($this->translate( + 'Now it\'s time to configure your first administrative account or group for Icinga Web 2.' + )); + } + + /** + * Set the resource configuration to use + * + * @param array $config + * + * @return $this + */ + public function setResourceConfig(array $config) + { + $this->resourceConfig = $config; + return $this; + } + + /** + * Set the user backend configuration to use + * + * @param array $config + * + * @return $this + */ + public function setBackendConfig(array $config) + { + $this->backendConfig = $config; + return $this; + } + + /** + * Set the user group backend configuration to use + * + * @param array $config + * + * @return $this + */ + public function setGroupConfig(array $config = null) + { + $this->groupConfig = $config; + return $this; + } + + /** + * @see Form::createElements() + */ + public function createElements(array $formData) + { + $choices = array(); + $groups = []; + if ($this->backendConfig['backend'] !== 'db') { + $choices['by_name'] = $this->translate('By Name', 'setup.admin'); + $choice = isset($formData['user_type']) ? $formData['user_type'] : 'by_name'; + + if (in_array($this->backendConfig['backend'], array('ldap', 'msldap'))) { + $groups = $this->fetchGroups(); + if (! empty($groups)) { + $choices['user_group'] = $this->translate('User Group', 'setup.admin'); + } + } + } else { + $choices['new_user'] = $this->translate('New User', 'setup.admin'); + $choice = isset($formData['user_type']) ? $formData['user_type'] : 'new_user'; + } + + $users = []; + if (in_array($this->backendConfig['backend'], array('db', 'ldap', 'msldap'))) { + $users = $this->fetchUsers(); + if (! empty($users)) { + $choices['existing_user'] = $this->translate('Existing User', 'setup.admin'); + } + } + + if (count($choices) > 1) { + $this->addElement( + 'select', + 'user_type', + array( + 'required' => true, + 'autosubmit' => true, + 'label' => $this->translate('Type Of Definition'), + 'description' => $this->translate('Choose how to define the desired account.'), + 'multiOptions' => $choices, + 'value' => $choice + ) + ); + } else { + $this->addElement( + 'hidden', + 'user_type', + array( + 'required' => true, + 'value' => key($choices) + ) + ); + } + + if ($choice === 'by_name') { + $this->addElement( + 'text', + 'by_name', + array( + 'required' => true, + 'value' => $this->getUsername(), + 'label' => $this->translate('Username'), + 'description' => $this->translate( + 'Define the initial administrative account by providing a username that reflects' + . ' a user created later or one that is authenticated using external mechanisms.' + ) + ) + ); + } + + if ($choice === 'user_group') { + $this->addElement( + 'select', + 'user_group', + array( + 'required' => true, + 'label' => $this->translate('Group Name'), + 'description' => $this->translate( + 'Choose a user group reported by the LDAP backend' + . ' to permit its members administrative access.', + 'setup.admin' + ), + 'multiOptions' => array_combine($groups, $groups) + ) + ); + } + + if ($choice === 'existing_user') { + $this->addElement( + 'select', + 'existing_user', + array( + 'required' => true, + 'label' => $this->translate('Username'), + 'description' => sprintf( + $this->translate( + 'Choose a user reported by the %s backend as the initial administrative account.', + 'setup.admin' + ), + $this->backendConfig['backend'] === 'db' + ? $this->translate('database', 'setup.admin.authbackend') + : 'LDAP' + ), + 'multiOptions' => array_combine($users, $users) + ) + ); + } + + if ($choice === 'new_user') { + $this->addElement( + 'text', + 'new_user', + array( + 'required' => true, + 'label' => $this->translate('Username'), + 'description' => $this->translate( + 'Enter the username to be used when creating an initial administrative account.' + ) + ) + ); + $this->addElement( + 'password', + 'new_user_password', + array( + 'required' => true, + 'renderPassword' => true, + 'label' => $this->translate('Password'), + 'description' => $this->translate( + 'Enter the password to assign to the newly created account.' + ), + 'autocomplete' => 'new-password' + ) + ); + $this->addElement( + 'password', + 'new_user_2ndpass', + array( + 'required' => true, + 'renderPassword' => true, + 'label' => $this->translate('Repeat password'), + 'description' => $this->translate( + 'Please repeat the password given above to avoid typing errors.' + ), + 'validators' => array( + array('identical', false, array('new_user_password')) + ) + ) + ); + } + } + + /** + * Validate the given request data and ensure that any new user does not already exist + * + * @param array $data The request data to validate + * + * @return bool + */ + public function isValid($data) + { + if (false === parent::isValid($data)) { + return false; + } + + if ($data['user_type'] === 'new_user' && $this->hasUser($data['new_user'])) { + $this->getElement('new_user')->addError($this->translate('Username already exists.')); + return false; + } + + return true; + } + + /** + * Return the name of the externally authenticated user + * + * @return string + */ + protected function getUsername() + { + list($name, $_) = ExternalBackend::getRemoteUserInformation(); + if ($name === null) { + return ''; + } + + if (isset($this->backendConfig['strip_username_regexp']) && $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 the user backend currently provides + * + * @return array + */ + protected function fetchUsers() + { + try { + $query = $this + ->createUserBackend() + ->select(array('user_name')) + ->order('user_name', 'asc', true); + if (in_array($this->backendConfig['backend'], array('ldap', 'msldap'))) { + /** @var LdapQuery $ldapQuery */ + $ldapQuery = $query->getQuery(); + $ldapQuery->setUsePagedResults(); + } + + return $query->fetchColumn(); + } catch (Exception $_) { + // No need to handle anything special here. Error means no users found. + return array(); + } + } + + /** + * Return whether the user backend provides a user with the given name + * + * @param string $username + * + * @return bool + */ + protected function hasUser($username) + { + try { + return $this + ->createUserBackend() + ->select() + ->where('user_name', $username) + ->count() > 1; + } catch (Exception $_) { + return false; + } + } + + /** + * Create and return the user backend + * + * @return DbUserBackend|LdapUserBackend + */ + protected function createUserBackend() + { + $resourceConfig = new Config(); + $resourceConfig->setSection($this->resourceConfig['name'], $this->resourceConfig); + ResourceFactory::setConfig($resourceConfig); + + $config = new ConfigObject($this->backendConfig); + $config->resource = $this->resourceConfig['name']; + return UserBackend::create(null, $config); + } + + /** + * Return the names of all user groups the user group backend currently provides + * + * @return array + */ + protected function fetchGroups() + { + try { + $query = $this + ->createUserGroupBackend() + ->select(array('group_name')); + if (in_array($this->backendConfig['backend'], array('ldap', 'msldap'))) { + /** @var LdapQuery $ldapQuery */ + $ldapQuery = $query->getQuery(); + $ldapQuery->setUsePagedResults(); + } + + return $query->fetchColumn(); + } catch (Exception $_) { + // No need to handle anything special here. Error means no groups found. + return array(); + } + } + + /** + * Return whether the user group backend provides a user group with the given name + * + * @param string $groupname + * + * @return bool + */ + protected function hasGroup($groupname) + { + try { + return $this + ->createUserGroupBackend() + ->select() + ->where('group_name', $groupname) + ->count() > 1; + } catch (Exception $_) { + return false; + } + } + + /** + * Create and return the user group backend + * + * @return LdapUserGroupBackend + */ + protected function createUserGroupBackend() + { + $resourceConfig = new Config(); + $resourceConfig->setSection($this->resourceConfig['name'], $this->resourceConfig); + ResourceFactory::setConfig($resourceConfig); + + $backendConfig = new Config(); + $backendConfig->setSection($this->backendConfig['name'], array_merge( + $this->backendConfig, + array('resource' => $this->resourceConfig['name']) + )); + UserBackend::setConfig($backendConfig); + + if (empty($this->groupConfig)) { + $groupConfig = new ConfigObject(array( + 'backend' => $this->backendConfig['backend'], // _Should_ be "db" or "msldap" + 'resource' => $this->resourceConfig['name'], + 'user_backend' => $this->backendConfig['name'] // Gets ignored if 'backend' is "db" + )); + } else { + $groupConfig = new ConfigObject($this->groupConfig); + } + + $backend = UserGroupBackend::create(null, $groupConfig); + if (! $backend instanceof Selectable) { + throw new NotImplementedError('Unsupported, until #9772 has been resolved'); + } + + return $backend; + } +} diff --git a/modules/setup/application/forms/AuthBackendPage.php b/modules/setup/application/forms/AuthBackendPage.php new file mode 100644 index 0000000..88c77e6 --- /dev/null +++ b/modules/setup/application/forms/AuthBackendPage.php @@ -0,0 +1,274 @@ +setName('setup_authentication_backend'); + $this->setTitle($this->translate('Authentication Backend', 'setup.page.title')); + $this->setValidatePartial(true); + } + + /** + * Set the resource configuration to use + * + * @param array $config + * + * @return $this + */ + public function setResourceConfig(array $config) + { + $resourceConfig = new Config(); + $resourceConfig->setSection($config['name'], $config); + ResourceFactory::setConfig($resourceConfig); + + $this->config = $config; + return $this; + } + + /** + * Create and add elements to this form + * + * @param array $formData + */ + public function createElements(array $formData) + { + if (isset($formData['skip_validation']) && $formData['skip_validation']) { + $this->addSkipValidationCheckbox(); + } + + $backendForm = null; + if (! isset($this->config) || $this->config['type'] === 'external') { + $backendForm = new ExternalBackendForm(); + $backendForm->create($formData); + $this->addDescription($this->translate( + 'You\'ve chosen to authenticate using a web server\'s mechanism so it may be necessary' + . ' to adjust usernames before any permissions, restrictions, etc. are being applied.' + )); + } elseif ($this->config['type'] === 'db') { + $this->setRequiredCue(null); + $backendForm = new DbBackendForm(); + $backendForm->setRequiredCue(null); + $backendForm->create($formData)->removeElement('resource'); + $this->addDescription($this->translate( + 'As you\'ve chosen to use a database for authentication all you need ' + . 'to do now is defining a name for your first authentication backend.' + )); + } elseif ($this->config['type'] === 'ldap') { + $type = null; + if (! isset($formData['type'])) { + if (isset($formData['backend'])) { + $formData['type'] = $type = $formData['backend']; + } elseif (isset($this->suggestions['backend'])) { + $formData['type'] = $type = $this->suggestions['backend']; + } + } + + $backendForm = new LdapBackendForm(); + $backendForm->setSuggestions($this->suggestions); + $backendForm->setResources(array($this->config['name'])); + $backendForm->create($formData); + $backendForm->getElement('resource')->setIgnore(true); + $this->addDescription($this->translate( + 'Before you are able to authenticate using the LDAP connection defined earlier you need to' + . ' provide some more information so that Icinga Web 2 is able to locate account details.' + )); + $this->addElement( + 'select', + 'type', + array( + 'ignore' => true, + 'required' => true, + 'autosubmit' => true, + 'label' => $this->translate('Backend Type'), + 'description' => $this->translate( + 'The type of the resource being used for this authenticaton provider' + ), + 'multiOptions' => array( + 'ldap' => 'LDAP', + 'msldap' => 'ActiveDirectory' + ), + 'value' => $type + ) + ); + } + + $backendForm->getElement('name')->setValue('icingaweb2'); + $this->addSubForm($backendForm, 'backend_form'); + } + + /** + * Retrieve all form element values + * + * @param bool $suppressArrayNotation Ignored + * + * @return array + */ + public function getValues($suppressArrayNotation = false) + { + $values = parent::getValues(); + $values = array_merge($values, $values['backend_form']); + unset($values['backend_form']); + return $values; + } + + /** + * 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 (! parent::isValid($data)) { + return false; + } + + if (isset($this->config)) { + if ($this->config['type'] === 'ldap' && ( + ! isset($data['skip_validation']) || $data['skip_validation'] == 0) + ) { + $self = clone $this; + $self->getSubForm('backend_form')->getElement('resource')->setIgnore(false); + $inspection = UserBackendConfigForm::inspectUserBackend($self); + if ($inspection && $inspection->hasError()) { + $this->error($inspection->getError()); + $this->addSkipValidationCheckbox(); + return false; + } + } + } + + return true; + } + + /** + * Run the configured backend's inspection checks and show the result, if necessary + * + * This will only run any validation if the user pushed the 'backend_validation' button. + * + * @param array $formData + * + * @return bool + */ + public function isValidPartial(array $formData) + { + if (isset($formData['backend_validation']) && parent::isValid($formData)) { + $self = clone $this; + if (($resourceElement = $self->getSubForm('backend_form')->getElement('resource')) !== null) { + $resourceElement->setIgnore(false); + } + + $inspection = UserBackendConfigForm::inspectUserBackend($self); + if ($inspection !== null) { + $join = function ($e) use (&$join) { + return is_string($e) ? $e : join("\n", array_map($join, $e)); + }; + $this->addElement( + 'note', + 'inspection_output', + array( + 'order' => 0, + 'value' => '' . $this->translate('Validation Log') . "\n\n" + . join("\n", array_map($join, $inspection->toArray())), + 'decorators' => array( + 'ViewHelper', + array('HtmlTag', array('tag' => 'pre', 'class' => 'log-output')), + ) + ) + ); + + if ($inspection->hasError()) { + $this->warning(sprintf( + $this->translate('Failed to successfully validate the configuration: %s'), + $inspection->getError() + )); + return false; + } + } + + $this->info($this->translate('The configuration has been successfully validated.')); + } elseif (isset($formData['discovery_btn']) || isset($formData['btn_discover_domain'])) { + return parent::isValidPartial($formData); + } elseif (! isset($formData['backend_validation'])) { + // This is usually done by isValid(Partial), but as we're not calling any of these... + $this->populate($formData); + } + + 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' => 0, + 'ignore' => true, + 'required' => true, + 'label' => $this->translate('Skip Validation'), + 'description' => $this->translate('Check this to not to validate authentication using this backend') + ) + ); + } + + /** + * Get default values for the subform's elements suggested by a previous step + * + * @return string[] + */ + public function getSuggestions() + { + return $this->suggestions; + } + + /** + * Set default values for the subform's elements suggested by a previous step + * + * @param string[] $suggestions + * + * @return $this + */ + public function setSuggestions(array $suggestions) + { + $this->suggestions = $suggestions; + + return $this; + } +} diff --git a/modules/setup/application/forms/AuthenticationPage.php b/modules/setup/application/forms/AuthenticationPage.php new file mode 100644 index 0000000..52e3c66 --- /dev/null +++ b/modules/setup/application/forms/AuthenticationPage.php @@ -0,0 +1,69 @@ +setRequiredCue(null); + $this->setName('setup_authentication_type'); + $this->setTitle($this->translate('Authentication', 'setup.page.title')); + $this->addDescription($this->translate( + 'Please choose how you want to authenticate when accessing Icinga Web 2.' + . ' Configuring backend specific details follows in a later step.' + )); + } + + /** + * @see Form::createElements() + */ + public function createElements(array $formData) + { + if (isset($formData['type']) && $formData['type'] === 'external') { + list($username, $_) = ExternalBackend::getRemoteUserInformation(); + if ($username === null) { + $this->info( + $this->translate( + 'You\'re currently not authenticated using any of the web server\'s authentication ' + . 'mechanisms. Make sure you\'ll configure such, otherwise you\'ll not be able to ' + . 'log into Icinga Web 2.' + ), + false + ); + } + } + + $backendTypes = array(); + if (Platform::hasMysqlSupport() || Platform::hasPostgresqlSupport()) { + $backendTypes['db'] = $this->translate('Database'); + } + if (Platform::extensionLoaded('ldap')) { + $backendTypes['ldap'] = 'LDAP'; + } + $backendTypes['external'] = $this->translate('External'); + + $this->addElement( + 'select', + 'type', + array( + 'required' => true, + 'autosubmit' => true, + 'label' => $this->translate('Authentication Type'), + 'description' => $this->translate('The type of authentication to use when accessing Icinga Web 2'), + 'multiOptions' => $backendTypes + ) + ); + } +} diff --git a/modules/setup/application/forms/DatabaseCreationPage.php b/modules/setup/application/forms/DatabaseCreationPage.php new file mode 100644 index 0000000..f7092a1 --- /dev/null +++ b/modules/setup/application/forms/DatabaseCreationPage.php @@ -0,0 +1,209 @@ +setTitle($this->translate('Database Setup', 'setup.page.title')); + $this->addDescription($this->translate( + 'It seems that either the database you defined earlier does not yet exist and cannot be created' + . ' using the provided access credentials, the database does not have the required schema to be' + . ' operated by Icinga Web 2 or the provided access credentials do not have the sufficient ' + . 'permissions to access the database. Please provide appropriate access credentials to solve this.' + )); + } + + /** + * Set the resource configuration to use + * + * @param array $config + * + * @return $this + */ + public function setResourceConfig(array $config) + { + $this->config = $config; + return $this; + } + + /** + * Set the required privileges to setup the database + * + * @param array $privileges The privileges + * + * @return $this + */ + public function setDatabaseSetupPrivileges(array $privileges) + { + $this->databaseSetupPrivileges = $privileges; + return $this; + } + + /** + * Set the required privileges to operate the database + * + * @param array $privileges The privileges + * + * @return $this + */ + public function setDatabaseUsagePrivileges(array $privileges) + { + $this->databaseUsagePrivileges = $privileges; + return $this; + } + + /** + * @see Form::createElements() + */ + public function createElements(array $formData) + { + $skipValidation = isset($formData['skip_validation']) && $formData['skip_validation']; + $this->addElement( + 'text', + 'username', + array( + 'required' => false === $skipValidation, + 'label' => $this->translate('Username'), + 'description' => $this->translate( + 'A user which is able to create databases and/or touch the database schema' + ) + ) + ); + $this->addElement( + 'password', + 'password', + array( + 'renderPassword' => true, + 'label' => $this->translate('Password'), + 'description' => $this->translate('The password for the database user defined above'), + 'autocomplete' => 'new-password' + ) + ); + + if ($skipValidation) { + $this->addSkipValidationCheckbox(); + } else { + $this->addElement( + 'hidden', + 'skip_validation', + array( + 'required' => true, + 'value' => 0 + ) + ); + } + } + + /** + * 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; + } + + if (isset($data['skip_validation']) && $data['skip_validation']) { + return true; + } + + $config = $this->config; + $config['username'] = $this->getValue('username'); + $config['password'] = $this->getValue('password'); + $db = new DbTool($config); + + try { + $db->connectToDb(); // Are we able to login on the database? + } catch (PDOException $_) { + try { + $db->connectToHost(); // Are we able to login on the server? + } catch (PDOException $e) { + // We are NOT able to login on the server.. + $this->error($e->getMessage()); + $this->addSkipValidationCheckbox(); + return false; + } + } + + // In case we are connected the credentials filled into this + // form need to be granted to create databases, users... + if (false === $db->checkPrivileges($this->databaseSetupPrivileges)) { + $this->error( + $this->translate('The provided credentials cannot be used to create the database and/or the user.') + ); + $this->addSkipValidationCheckbox(); + return false; + } + + // ...and to grant all required usage privileges to others + if (false === $db->isGrantable($this->databaseUsagePrivileges)) { + $this->error(sprintf( + $this->translate( + 'The provided credentials cannot be used to grant all required privileges to the login "%s".' + ), + $this->config['username'] + )); + $this->addSkipValidationCheckbox(); + return false; + } + + return true; + } + + /** + * Add a checkbox to the form by which the user can skip the login and privilege validation + */ + protected function addSkipValidationCheckbox() + { + $this->addElement( + 'checkbox', + 'skip_validation', + array( + 'order' => 0, + 'required' => true, + 'label' => $this->translate('Skip Validation'), + 'description' => $this->translate( + 'Check this to not to validate the ability to login and required privileges' + ) + ) + ); + } +} diff --git a/modules/setup/application/forms/DbResourcePage.php b/modules/setup/application/forms/DbResourcePage.php new file mode 100644 index 0000000..a417710 --- /dev/null +++ b/modules/setup/application/forms/DbResourcePage.php @@ -0,0 +1,183 @@ +setTitle($this->translate('Database Resource', 'setup.page.title')); + $this->setValidatePartial(true); + } + + /** + * @see Form::createElements() + */ + public function createElements(array $formData) + { + $this->addElement( + 'hidden', + 'type', + array( + 'required' => true, + 'value' => 'db' + ) + ); + + 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()); + $this->getElement('name')->setValue('icingaweb_db'); + } + + /** + * 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) { + if (! $this->validateConfiguration()) { + $this->addSkipValidationCheckbox(); + return false; + } + } + + return true; + } + + /** + * Check whether it's possible to connect to the database server + * + * This will only run the check if the user pushed the 'backend_validation' button. + * + * @param array $formData + * + * @return bool + */ + public function isValidPartial(array $formData) + { + if (isset($formData['backend_validation']) && parent::isValid($formData)) { + if (! $this->validateConfiguration()) { + return false; + } + + $this->info($this->translate('The configuration has been successfully validated.')); + } elseif (! isset($formData['backend_validation'])) { + // This is usually done by isValid(Partial), but as we're not calling any of these... + $this->populate($formData); + } + + return true; + } + + /** + * Return whether the configuration is valid + * + * @return bool + */ + protected function validateConfiguration() + { + try { + $db = new DbTool($this->getValues()); + $db->checkConnectivity(); + } catch (Exception $e) { + $this->error(sprintf( + $this->translate('Failed to successfully validate the configuration: %s'), + $e->getMessage() + )); + return false; + } + + $state = true; + $connectionError = null; + + try { + $db->connectToDb(); + } catch (Exception $e) { + $connectionError = $e; + } + + if ($connectionError === null && array_search('icinga_instances', $db->listTables(), true) !== false) { + $this->warning($this->translate( + 'The database you\'ve configured to use for Icinga Web 2 seems to be the one of Icinga. Please be aware' + . ' that this database configuration is supposed to be used for Icinga Web 2\'s configuration and that' + . ' it is highly recommended to not mix different schemas in the same database. If this is intentional,' + . ' you can skip the validation and ignore this warning. If not, please provide a different database.' + )); + $state = false; + } + + if ($this->getValue('db') === 'pgsql') { + if ($connectionError !== null) { +// $this->warning(sprintf( +// $this->translate('Unable to check the server\'s version. This is usually not a critical error' +// . ' as there is probably only access to the database permitted which does not exist yet. If you are' +// . ' absolutely sure you are running PostgreSQL in a version equal to or newer than 9.1,' +// . ' you can skip the validation and safely proceed to the next step. The error was: %s'), +// $connectionError->getMessage() +// )); +// $state = false; + } else { + $version = $db->getServerVersion(); + if (version_compare($version, '9.1', '<')) { + $this->error(sprintf( + $this->translate('The server\'s version %s is too old. The minimum required version is %s.'), + $version, + '9.1' + )); + $state = false; + } + } + } + + return $state; + } + + /** + * Add a checkbox to the form by which the user can skip the configuration validation + */ + protected function addSkipValidationCheckbox() + { + $this->addElement( + 'checkbox', + 'skip_validation', + array( + 'required' => true, + 'label' => $this->translate('Skip Validation'), + 'description' => $this->translate('Check this to not to validate the configuration') + ) + ); + } +} diff --git a/modules/setup/application/forms/GeneralConfigPage.php b/modules/setup/application/forms/GeneralConfigPage.php new file mode 100644 index 0000000..5b9f011 --- /dev/null +++ b/modules/setup/application/forms/GeneralConfigPage.php @@ -0,0 +1,41 @@ +setName('setup_general_config'); + $this->setTitle($this->translate('Application Configuration', 'setup.page.title')); + $this->addDescription($this->translate( + 'Now please adjust all application and logging related configuration options to fit your needs.' + )); + } + + /** + * @see Form::createElements() + */ + public function createElements(array $formData) + { + $appConfigForm = new ApplicationConfigForm(); + $appConfigForm->createElements($formData); + $appConfigForm->removeElement('global_module_path'); + $appConfigForm->removeElement('global_config_resource'); + $this->addElements($appConfigForm->getElements()); + + $loggingConfigForm = new LoggingConfigForm(); + $this->addElements($loggingConfigForm->createElements($formData)->getElements()); + } +} diff --git a/modules/setup/application/forms/LdapDiscoveryConfirmPage.php b/modules/setup/application/forms/LdapDiscoveryConfirmPage.php new file mode 100644 index 0000000..33bc907 --- /dev/null +++ b/modules/setup/application/forms/LdapDiscoveryConfirmPage.php @@ -0,0 +1,133 @@ + + Type:{type} + Port:{port} + Root DN:{root_dn} + User Object Class:{user_class} + User Name Attribute:{user_attribute} + +EOT; + + /** + * The previous configuration + * + * @var array + */ + private $config; + + /** + * Initialize this page + */ + public function init() + { + $this->setName('setup_ldap_discovery_confirm'); + $this->setTitle($this->translate('LDAP Discovery Results', 'setup.page.title')); + } + + /** + * Set the resource configuration to use + * + * @param array $config + * + * @return $this + */ + public function setResourceConfig(array $config) + { + $this->config = $config; + return $this; + } + + /** + * Return the resource configuration as Config object + * + * @return ConfigObject + */ + public function getResourceConfig() + { + return new ConfigObject($this->config); + } + + /** + * @see Form::createElements() + */ + public function createElements(array $formData) + { + $resource = $this->config['resource']; + $backend = $this->config['backend']; + $html = $this->infoTemplate; + $html = str_replace('{type}', $this->config['type'], $html); + $html = str_replace('{hostname}', $resource['hostname'], $html); + $html = str_replace('{port}', $resource['port'], $html); + $html = str_replace('{root_dn}', $resource['root_dn'], $html); + $html = str_replace('{user_attribute}', $backend['user_name_attribute'], $html); + $html = str_replace('{user_class}', $backend['user_class'], $html); + + $this->addDescription(sprintf( + $this->translate('The following directory service has been found on domain "%s".'), + $this->config['domain'] + )); + + $this->addElement( + 'note', + 'suggestion', + array( + 'value' => $html, + 'decorators' => array( + 'ViewHelper', + array( + 'HtmlTag', array('tag' => 'div') + ) + ) + ) + ); + + $this->addElement( + 'checkbox', + 'confirm', + array( + 'value' => '1', + 'label' => $this->translate('Use this configuration?') + ) + ); + } + + /** + * 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; + } + return true; + } + + public function getValues($suppressArrayNotation = false) + { + if ($this->getValue('confirm') === '1') { + // use configuration + return $this->config; + } + return null; + } +} diff --git a/modules/setup/application/forms/LdapDiscoveryPage.php b/modules/setup/application/forms/LdapDiscoveryPage.php new file mode 100644 index 0000000..7b5de17 --- /dev/null +++ b/modules/setup/application/forms/LdapDiscoveryPage.php @@ -0,0 +1,115 @@ +setName('setup_ldap_discovery'); + $this->setTitle($this->translate('LDAP Discovery', 'setup.page.title')); + $this->addDescription($this->translate( + 'You can use this page to discover LDAP or ActiveDirectory servers ' . + ' for authentication. If you don\'t want to execute a discovery, just skip this step.' + )); + } + + /** + * @see Form::createElements() + */ + public function createElements(array $formData) + { + $discoveryForm = new LdapDiscoveryForm(); + $this->addElements($discoveryForm->createElements($formData)->getElements()); + + $this->addElement( + 'checkbox', + 'skip_validation', + array( + 'label' => $this->translate('Skip'), + 'description' => $this->translate('Do not discover LDAP servers and enter all settings manually.') + ) + ); + } + + /** + * 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 (isset($data['skip_validation']) && $data['skip_validation']) { + return true; + } + + if (isset($data['domain']) && $data['domain']) { + try { + $this->discovery = Discovery::discoverDomain($data['domain']); + if ($this->discovery->isSuccess()) { + return true; + } else { + $this->error($this->discovery->getError()->getMessage()); + } + } catch (Exception $e) { + $this->error(sprintf( + $this->translate('Could not find any LDAP servers on the domain "%s". An error occurred: %s'), + $data['domain'], + IcingaException::describe($e) + )); + } + } else { + $labeller = new ErrorLabeller(array('element' => $this->getElement('domain'))); + $this->getElement('domain')->addError($labeller->translate(Zend_Validate_NotEmpty::IS_EMPTY)); + } + + return false; + } + + /** + * Suggest settings based on the underlying discovery + * + * @param bool $suppressArrayNotation + * + * @return array + */ + public function getValues($suppressArrayNotation = false) + { + if (! isset($this->discovery) || ! $this->discovery->isSuccess()) { + return []; + } + $disc = $this->discovery; + return array( + 'domain' => $this->getValue('domain'), + 'type' => $disc->isAd() ? LdapDiscoveryConfirmPage::TYPE_AD : LdapDiscoveryConfirmPage::TYPE_MISC, + 'resource' => $disc->suggestResourceSettings(), + 'backend' => $disc->suggestBackendSettings() + ); + } +} diff --git a/modules/setup/application/forms/LdapResourcePage.php b/modules/setup/application/forms/LdapResourcePage.php new file mode 100644 index 0000000..7786407 --- /dev/null +++ b/modules/setup/application/forms/LdapResourcePage.php @@ -0,0 +1,152 @@ +setName('setup_ldap_resource'); + $this->setTitle($this->translate('LDAP Resource', 'setup.page.title')); + $this->addDescription($this->translate( + 'Now please configure your AD/LDAP resource. This will later ' + . 'be used to authenticate users logging in to Icinga Web 2.' + )); + $this->setValidatePartial(true); + } + + /** + * @see Form::createElements() + */ + public function createElements(array $formData) + { + $this->addElement( + 'hidden', + 'type', + array( + 'required' => true, + 'value' => 'ldap' + ) + ); + + 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()); + $this->getElement('name')->setValue('icingaweb_ldap'); + } + + /** + * 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 (! parent::isValid($data)) { + return false; + } + + if (! isset($data['skip_validation']) || $data['skip_validation'] == 0) { + $inspection = ResourceConfigForm::inspectResource($this); + if ($inspection !== null && $inspection->hasError()) { + $this->error($inspection->getError()); + $this->addSkipValidationCheckbox(); + return false; + } + } + + return true; + } + + /** + * Run the configured backend's inspection checks and show the result, if necessary + * + * This will only run any validation if the user pushed the 'backend_validation' button. + * + * @param array $formData + * + * @return bool + */ + public function isValidPartial(array $formData) + { + if (isset($formData['backend_validation']) && parent::isValid($formData)) { + $inspection = ResourceConfigForm::inspectResource($this); + if ($inspection !== null) { + $join = function ($e) use (&$join) { + return is_string($e) ? $e : join("\n", array_map($join, $e)); + }; + $this->addElement( + 'note', + 'inspection_output', + array( + 'order' => 0, + 'value' => '' . $this->translate('Validation Log') . "\n\n" + . join("\n", array_map($join, $inspection->toArray())), + 'decorators' => array( + 'ViewHelper', + array('HtmlTag', array('tag' => 'pre', 'class' => 'log-output')), + ) + ) + ); + + if ($inspection->hasError()) { + $this->warning(sprintf( + $this->translate('Failed to successfully validate the configuration: %s'), + $inspection->getError() + )); + return false; + } + } + + $this->info($this->translate('The configuration has been successfully validated.')); + } elseif (! isset($formData['backend_validation'])) { + // This is usually done by isValid(Partial), but as we're not calling any of these... + $this->populate($formData); + } + + 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' => $this->translate('Skip Validation'), + 'description' => $this->translate( + 'Check this to not to validate connectivity with the given directory service' + ) + ) + ); + } +} diff --git a/modules/setup/application/forms/ModulePage.php b/modules/setup/application/forms/ModulePage.php new file mode 100644 index 0000000..d62b5a9 --- /dev/null +++ b/modules/setup/application/forms/ModulePage.php @@ -0,0 +1,108 @@ +setName('setup_modules'); + $this->setViewScript('form/setup-modules.phtml'); + + $this->modulePaths = array(); + if (($appModulePath = realpath(Icinga::app()->getApplicationDir() . '/../modules')) !== false) { + $this->modulePaths[] = $appModulePath; + } + } + + public function createElements(array $formData) + { + foreach ($this->getModules() as $module) { + $checked = false; + if ($module->getName() === 'monitoring') { + $checked = ! $this->foundIcingaDB; + } elseif ($this->foundIcingaDB && $module->getName() === 'icingadb') { + $checked = true; + } + + $this->addElement( + 'checkbox', + $module->getName(), + array( + 'description' => $module->getDescription(), + 'label' => ucfirst($module->getName()), + 'value' => (int) $checked, + 'decorators' => array('ViewHelper') + ) + ); + } + } + + /** + * @return Module[] + */ + protected function getModules() + { + if ($this->modules !== null) { + return $this->modules; + } else { + $this->modules = array(); + } + + $moduleManager = Icinga::app()->getModuleManager(); + $moduleManager->detectInstalledModules($this->modulePaths); + foreach ($moduleManager->listInstalledModules() as $moduleName) { + if ($moduleName !== 'setup') { + $this->modules[$moduleName] = $moduleManager->loadModule($moduleName)->getModule($moduleName); + } + + if ($moduleName === 'icingadb') { + $this->foundIcingaDB = true; + } + } + + return $this->modules; + } + + public function getCheckedModules() + { + $modules = $this->getModules(); + + $checked = array(); + foreach ($this->getElements() as $name => $element) { + if (array_key_exists($name, $modules) && $element->isChecked()) { + $checked[$name] = $modules[$name]; + } + } + + return $checked; + } + + public function getModuleWizards() + { + $checked = $this->getCheckedModules(); + + $wizards = array(); + foreach ($checked as $name => $module) { + if ($module->providesSetupWizard()) { + $wizards[$name] = $module->getSetupWizard(); + } + } + + return $wizards; + } +} diff --git a/modules/setup/application/forms/RequirementsPage.php b/modules/setup/application/forms/RequirementsPage.php new file mode 100644 index 0000000..d1fb70e --- /dev/null +++ b/modules/setup/application/forms/RequirementsPage.php @@ -0,0 +1,68 @@ +setName('setup_requirements'); + $this->setViewScript('form/setup-requirements.phtml'); + } + + /** + * Set the wizard + * + * @param SetupWizard $wizard + * + * @return $this + */ + public function setWizard(SetupWizard $wizard) + { + $this->wizard = $wizard; + return $this; + } + + /** + * Return the wizard + * + * @return SetupWizard + */ + public function getWizard() + { + return $this->wizard; + } + + /** + * Validate the given form data and check whether the wizard's 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->wizard->getRequirements()->fulfilled(); + } +} diff --git a/modules/setup/application/forms/SummaryPage.php b/modules/setup/application/forms/SummaryPage.php new file mode 100644 index 0000000..ab62d55 --- /dev/null +++ b/modules/setup/application/forms/SummaryPage.php @@ -0,0 +1,84 @@ +getName() === $this->filterName(get_class($this))) { + throw new LogicException( + 'When utilizing ' . get_class($this) . ' it is required to set a unique name by using the form options' + ); + } + + $this->setViewScript('form/setup-summary.phtml'); + } + + /** + * Set the title of what is being set up + * + * @param string $title + */ + public function setSubjectTitle($title) + { + $this->title = $title; + } + + /** + * Return the title of what is being set up + * + * @return string + */ + public function getSubjectTitle() + { + return $this->title; + } + + /** + * Set the summary to show + * + * @param array $summary + * + * @return $this + */ + public function setSummary(array $summary) + { + $this->summary = $summary; + return $this; + } + + /** + * Return the summary to show + * + * @return array + */ + public function getSummary() + { + return $this->summary; + } +} diff --git a/modules/setup/application/forms/UserGroupBackendPage.php b/modules/setup/application/forms/UserGroupBackendPage.php new file mode 100644 index 0000000..751270f --- /dev/null +++ b/modules/setup/application/forms/UserGroupBackendPage.php @@ -0,0 +1,147 @@ +setName('setup_usergroup_backend'); + $this->setTitle($this->translate('User Group Backend', 'setup.page.title')); + $this->addDescription($this->translate( + 'To allow Icinga Web 2 to associate users and groups, you\'ll need to provide some further information' + . ' about the LDAP Connection that is already going to be used to locate account details.' + )); + } + + /** + * Set the resource configuration to use + * + * @param array $config + * + * @return $this + */ + public function setResourceConfig(array $config) + { + $this->resourceConfig = $config; + return $this; + } + + /** + * Set the user backend configuration to use + * + * @param array $config + * + * @return $this + */ + public function setBackendConfig(array $config) + { + $this->backendConfig = $config; + return $this; + } + + /** + * Return the resource configuration as Config object + * + * @return Config + */ + protected function createResourceConfiguration() + { + $config = new Config(); + $config->setSection($this->resourceConfig['name'], $this->resourceConfig); + return $config; + } + + /** + * Return the user backend configuration as Config object + * + * @return Config + */ + protected function createBackendConfiguration() + { + $config = new Config(); + $backendConfig = $this->backendConfig; + $backendConfig['resource'] = $this->resourceConfig['name']; + $config->setSection($this->backendConfig['name'], $backendConfig); + return $config; + } + + /** + * Create and add elements to this form + * + * @param array $formData + */ + public function createElements(array $formData) + { + // LdapUserGroupBackendForm requires these factories to provide valid configurations + ResourceFactory::setConfig($this->createResourceConfiguration()); + UserBackend::setConfig($this->createBackendConfiguration()); + + $backendForm = new LdapUserGroupBackendForm(); + $formData['type'] = 'ldap'; + $backendForm->create($formData); + $backendForm->getElement('name')->setValue('icingaweb2'); + $this->addSubForm($backendForm, 'backend_form'); + + $backendForm->addElement( + 'hidden', + 'resource', + array( + 'required' => true, + 'value' => $this->resourceConfig['name'], + 'decorators' => array('ViewHelper') + ) + ); + $backendForm->addElement( + 'hidden', + 'user_backend', + array( + 'required' => true, + 'value' => $this->backendConfig['name'], + 'decorators' => array('ViewHelper') + ) + ); + } + + /** + * Retrieve all form element values + * + * @param bool $suppressArrayNotation Ignored + * + * @return array + */ + public function getValues($suppressArrayNotation = false) + { + $values = parent::getValues(); + $values = array_merge($values, $values['backend_form']); + unset($values['backend_form']); + return $values; + } +} diff --git a/modules/setup/application/forms/WelcomePage.php b/modules/setup/application/forms/WelcomePage.php new file mode 100644 index 0000000..124a31f --- /dev/null +++ b/modules/setup/application/forms/WelcomePage.php @@ -0,0 +1,45 @@ +setRequiredCue(null); + $this->setName('setup_welcome'); + $this->setViewScript('form/setup-welcome.phtml'); + } + + /** + * @see Form::createElements() + */ + public function createElements(array $formData) + { + $this->addElement( + 'text', + 'token', + array( + 'class' => 'autofocus', + 'required' => true, + 'label' => $this->translate('Setup Token'), + 'description' => $this->translate( + 'For security reasons we need to ensure that you are permitted to run this wizard.' + . ' Please provide a token by following the instructions below.' + ), + 'validators' => array(new TokenValidator(Icinga::app()->getConfigDir() . '/setup.token')) + ) + ); + } +} diff --git a/modules/setup/application/views/scripts/form/setup-modules.phtml b/modules/setup/application/views/scripts/form/setup-modules.phtml new file mode 100644 index 0000000..e57c7dc --- /dev/null +++ b/modules/setup/application/views/scripts/form/setup-modules.phtml @@ -0,0 +1,33 @@ + +
+

translate('Modules', 'setup.page.title'); ?>

+

translate('The following modules were found in your Icinga Web 2 installation. To enable and configure a module, just tick it and click "Next".'); ?>

+getElements() as $element): ?> + getName(), array(Wizard::BTN_PREV, Wizard::BTN_NEXT, Wizard::PROGRESS_ELEMENT, $form->getTokenElementName(), $form->getUidElementName()))): ?> +
+
+

+
+ +
+
+ +
+ + + getElement($form->getTokenElementName()); ?> + getElement($form->getUidElementName()); ?> + getDisplayGroup('buttons'); ?> +
diff --git a/modules/setup/application/views/scripts/form/setup-requirements.phtml b/modules/setup/application/views/scripts/form/setup-requirements.phtml new file mode 100644 index 0000000..544f284 --- /dev/null +++ b/modules/setup/application/views/scripts/form/setup-requirements.phtml @@ -0,0 +1,48 @@ +getWizard()->getRequirements()->fulfilled()) { + $form->getElement(Wizard::BTN_NEXT)->setAttrib('disabled', 1); +} + +?> +

Icinga Web 2

+getWizard()->getRequirements(true); ?> +getWizard()->getPage('setup_modules')->getModuleWizards() as $moduleName => $wizard): ?> +

translate('Module'); ?>

+getRequirements(); ?> + +
+ getElement($form->getTokenElementName()); ?> + getElement($form->getUidElementName()); ?> +
+ getElement(Wizard::BTN_NEXT); + echo $double->setAttrib('class', 'double'); + ?> + getElement(Wizard::BTN_PREV); ?> + getElement(Wizard::BTN_NEXT); ?> + getElement(Wizard::PROGRESS_ELEMENT); ?> +
+ translate('You may also need to restart the web-server for the changes to take effect!'); ?> + qlink( + $this->translate('Refresh'), + null, + null, + array( + 'class' => 'button-link', + 'title' => $title, + 'aria-label' => sprintf($this->translate('Refresh the page; %s'), $title) + ) + ); ?> +
+
+
\ No newline at end of file diff --git a/modules/setup/application/views/scripts/form/setup-summary.phtml b/modules/setup/application/views/scripts/form/setup-summary.phtml new file mode 100644 index 0000000..3ad0265 --- /dev/null +++ b/modules/setup/application/views/scripts/form/setup-summary.phtml @@ -0,0 +1,40 @@ +getElement(Wizard::BTN_NEXT)->setAttrib( + 'class', + $form->getElement(Wizard::BTN_NEXT)->getAttrib('class') . ' finish' +); + +?> +

translate( + 'You\'ve configured %1$s successfully. You can review the changes supposed to be made before setting it up.' + . ' Make sure that everything is correct (Feel free to navigate back to make any corrections!) so' + . ' that you can start using %1$s right after it has successfully been set up.' + ), + $form->getSubjectTitle() +); ?>

+
+getSummary() as $pageHtml): ?> + +
+ +
+ + +
+
+ getElement($form->getTokenElementName()); ?> + getElement($form->getUidElementName()); ?> + getDisplayGroup('buttons'); ?> +
\ No newline at end of file diff --git a/modules/setup/application/views/scripts/form/setup-welcome.phtml b/modules/setup/application/views/scripts/form/setup-welcome.phtml new file mode 100644 index 0000000..1be68f3 --- /dev/null +++ b/modules/setup/application/views/scripts/form/setup-welcome.phtml @@ -0,0 +1,120 @@ +getConfigDir(); +$setupTokenPath = rtrim($configDir, '/') . '/setup.token'; +$cliPath = realpath(Icinga::app()->getApplicationDir() . '/../bin/icingacli'); + +$groupadd = null; +$docker = getenv('ICINGAWEB_OFFICIAL_DOCKER_IMAGE'); + +if (! (false === ($distro = Platform::getLinuxDistro(1)) || $distro === 'linux')) { + foreach (array( + 'groupadd -r icingaweb2' => array( + 'redhat', 'rhel', 'centos', 'fedora', + 'suse', 'sles', 'sled', 'opensuse' + ), + 'addgroup --system icingaweb2' => array('debian', 'ubuntu') + ) as $groupadd_ => $distros) { + if (in_array($distro, $distros)) { + $groupadd = $groupadd_; + break; + } + } + + switch ($distro) { + case 'redhat': + case 'rhel': + case 'centos': + case 'fedora': + $usermod = 'usermod -a -G icingaweb2 %s'; + $webSrvUser = 'apache'; + break; + case 'suse': + case 'sles': + case 'sled': + case 'opensuse': + $usermod = 'usermod -A icingaweb2 %s'; + $webSrvUser = 'wwwrun'; + break; + case 'debian': + case 'ubuntu': + $usermod = 'usermod -a -G icingaweb2 %s'; + $webSrvUser = 'www-data'; + break; + default: + $usermod = $webSrvUser = null; + } +} +?> +
+

translate('Welcome to the configuration of Icinga Web 2!') ?>

+ +

translate( + 'You\'ve already completed the configuration of Icinga Web 2. Note that most of your configuration' + . ' files will be overwritten in case you\'ll re-configure Icinga Web 2 using this wizard!' + ); ?>

+ +

translate( + 'This wizard will guide you through the configuration of Icinga Web 2. Once completed and successfully' + . ' finished you are able to log in and to explore all the new and stunning features!' + ); ?>

+ +
+ getElement('token'); ?> + getElement($form->getTokenElementName()); ?> + getElement($form->getUidElementName()); ?> +
+ getElement(Wizard::BTN_NEXT); ?> +
+
+
+

translate('Generating a New Setup Token'); ?>

+
+

translate( + 'To run this wizard a user needs to authenticate using a token which is usually' + . ' provided to him by an administrator who\'d followed the instructions below.' + ); ?>

+ +

translate('In any case, make sure that all of the following applies to your environment:'); ?>

+
    +
  • translate('A system group called "icingaweb2" exists'); ?>
  • + +
  • translate('The user "%s" is a member of the system group "icingaweb2"'), $phpUser); ?>
  • + +
  • translate('Your webserver\'s user is a member of the system group "icingaweb2"'); ?>
  • + +
+ +
+ escape($groupadd . ';') ?> + escape(sprintf($usermod, $phpUser ?: $webSrvUser) . ';') ?> +
+ +

translate('If you\'ve got the IcingaCLI installed you can do the following:'); ?>

+ +
+ + setup config directory --group icingaweb2; + + setup token create; +
+ +

translate('In case the IcingaCLI is missing you can create the token manually:'); ?>

+
+ su translate(''); ?> -s /bin/sh -c "mkdir -m 2770 ; chgrp icingaweb2 ; head -c 12 /dev/urandom | base64 | tee ; chmod 0660 ;"; +
+ +

translate('Please see the %s for an extensive description on how to access and use this wizard.'), + '' . $this->translate('Icinga Web 2 documentation') . '' // TODO: Add link to iw2 docs which points to the installation topic + ); ?>

+
+
+
diff --git a/modules/setup/application/views/scripts/index/index.phtml b/modules/setup/application/views/scripts/index/index.phtml new file mode 100644 index 0000000..32952e7 --- /dev/null +++ b/modules/setup/application/views/scripts/index/index.phtml @@ -0,0 +1,224 @@ +getPages(); +$finished = isset($success); +$configPages = array_slice($pages, 3, count($pages) - 4, true); +$currentPos = array_search($wizard->getCurrentPage(), $pages, true); +list($configPagesLeft, $configPagesRight) = array_chunk($configPages, (int)(count($configPages) / 2), true); +$setupStyle = (new Style()) + ->setSelector('.setup-header > .progress-bar') + ->setNonce(Csp::getStyleNonce()); + +$visitedPages = array_keys($wizard->getPageData()); +$maxProgress = max(array_merge([0], array_keys(array_filter( + $pages, + function ($page) use ($visitedPages) { return in_array($page->getName(), $visitedPages); } +)))); + +$setupStyle->add( + '.width-percent-10', + ['width' => '10%'] +)->add( + '.width-percent-60', + ['width' => '60%'] +); +?> +
+
+ img('img/icinga-logo-big.png'); ?> +
+
+

translate('Welcome', 'setup.progress'); ?>

+ 0 ? 'complete' : ( + $maxProgress > 0 ? 'visited' : 'active' + ); ?> + + + + +
+
+
+

translate('Modules', 'setup.progress'); ?>

+ 1 ? ' complete' : ( + $maxProgress > 1 ? ' visited' : ( + $currentPos === 1 ? ' active' : '' + ) + ); ?> + + + + +
+ = $currentPos && $maxProgress === 1)): ?> + restartForm ?> + +
+
+

translate('Requirements', 'setup.progress'); ?>

+ 2 ? ' complete' : ( + $maxProgress > 2 ? ' visited' : ( + $currentPos === 2 ? ' active' : '' + ) + ); ?> + + + + +
+ = $currentPos && $maxProgress === 2)): ?> + restartForm ?> + +
+
+

translate('Configuration', 'setup.progress'); ?>

+ + + + +
+ + $page): ?> + 2 ? ' active' : '') + ); ?> + + add( + '.step .left-line-' . $pos, + [ + 'float' => 'left', + 'width' => sprintf( + '%.2F%%', + 100 - (count($configPagesLeft) - 1) * $lineWidth + ), + 'margin-right' => 0 + ] + ); + ?> +
+ + add( + '.step .left-line-' . $pos, + [ + 'float' => 'left', + 'width' => $lineWidth . '%', + 'margin-right' => '-0.1em' + ] + ); + ?> +
+ + add( + '.step .left-line-' . $pos, + [ + 'float' => 'left', + 'width' => $lineWidth . '%' + ] + ); + ?> +
+ + +
+
+
+ + $page): ?> + 2 ? ' active' : '') + ); ?> + + add( + '.step .right-line-' . $pos, + [ + 'float' => 'left', + 'width' => $lineWidth . '%', + 'margin-right' => '-0.1em' + ] + ); + ?> +
+ + add( + '.step .right-line-' . $pos, + [ + 'float' => 'left', + 'width' => sprintf( + '%.2F%%', + 100 - (count($configPagesRight) - 1) * $lineWidth + ), + 'margin-right' => 0 + ] + ); + ?> +
+ + add( + '.step .right-line-' . $pos, + [ + 'float' => 'left', + 'width' => $lineWidth . '%' + ] + ); + ?> +
+ + +
+ 2 || $currentPos > 2): ?> + restartForm ?> + +
+
+

translate('Finish', 'setup.progress'); ?>

+ + + + + +
+
+
+
+
+ + render('index/parts/finish.phtml'); ?> + + render('index/parts/wizard.phtml'); ?> + +
+
+ + diff --git a/modules/setup/application/views/scripts/index/parts/finish.phtml b/modules/setup/application/views/scripts/index/parts/finish.phtml new file mode 100644 index 0000000..dcb34dc --- /dev/null +++ b/modules/setup/application/views/scripts/index/parts/finish.phtml @@ -0,0 +1,34 @@ +
+ +

translate('Congratulations! Icinga Web 2 has been successfully set up.'); ?>

+ +

translate('Sorry! Failed to set up Icinga Web 2 successfully.'); ?>

+ +
+ + qlink( + $this->translate('Login to Icinga Web 2'), + 'authentication/login', + null, + array( + 'class' => 'button-link login', + 'data-no-icinga-ajax' => true, + 'title' => $this->translate('Show the login page of Icinga Web 2') + ) + ); ?> + + qlink( + $this->translate('Back'), + null, + null, + array( + 'class' => 'button-link', + 'title' => $this->translate('Show previous wizard-page') + ) + ); ?> + +
+
+
diff --git a/modules/setup/application/views/scripts/index/parts/wizard.phtml b/modules/setup/application/views/scripts/index/parts/wizard.phtml new file mode 100644 index 0000000..94891f9 --- /dev/null +++ b/modules/setup/application/views/scripts/index/parts/wizard.phtml @@ -0,0 +1 @@ +getForm()->render(); ?> \ No newline at end of file -- cgit v1.2.3