From b842a396503f0df8dd7771d07b4bf31069e9a537 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 26 May 2015 18:23:26 +0200 Subject: [PATCH 001/116] Implement ::getLinuxDistro() refs #8705 --- library/Icinga/Application/Platform.php | 118 ++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/library/Icinga/Application/Platform.php b/library/Icinga/Application/Platform.php index 355311b97..a6e0d9604 100644 --- a/library/Icinga/Application/Platform.php +++ b/library/Icinga/Application/Platform.php @@ -3,6 +3,8 @@ namespace Icinga\Application; +use Icinga\Exception\IcingaException; + /** * Platform tests for icingaweb */ @@ -59,6 +61,122 @@ class Platform return strtoupper(substr(self::getOperatingSystemName(), 0, 5)) === 'LINUX'; } + /** + * Return the Linux distribution's name + * or 'linux' if the name could not be found out + * or false if the OS isn't Linux or an error occurred + * + * @param int $reliable + * 3: Only parse /etc/os-release (or /usr/lib/os-release). + * For the paranoid ones. + * 2: If that (3) doesn't help, check /etc/*-release, too. + * If something is unclear, return 'linux'. + * 1: Almost equal to mode 2. The possible return values also include: + * 'redhat' -- unclear whether RHEL/Fedora/... + * 'suse' -- unclear whether SLES/openSUSE/... + * 0: If even that (1) doesn't help, check /proc/version, too. + * This may not work (as expected) on LXC containers! + * (No reliability at all!) + * + * @return string|bool + */ + public static function getLinuxDistro($reliable = 2) + { + if (! self::isLinux()) { + return false; + } + + foreach (array('/etc/os-release', '/usr/lib/os-release') as $osReleaseFile) { + if (false === ($osRelease = @file( + $osReleaseFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES + ))) { + continue; + } + + foreach ($osRelease as $osInfo) { + if (false === ($res = @preg_match('/(? 2) { + return 'linux'; + } + + foreach (array( + 'fedora' => '/etc/fedora-release', + 'centos' => '/etc/centos-release' + ) as $distro => $releaseFile) { + if (! (false === ( + $release = @file_get_contents($releaseFile) + ) || false === strpos(strtolower($release), $distro))) { + return $distro; + } + } + + if (false !== ($release = @file_get_contents('/etc/redhat-release'))) { + $release = strtolower($release); + if (false !== strpos($release, 'red hat enterprise linux')) { + return 'rhel'; + } + foreach (array('fedora', 'centos') as $distro) { + if (false !== strpos($release, $distro)) { + return $distro; + } + } + return $reliable < 2 ? 'redhat' : 'linux'; + } + + if (false !== ($release = @file_get_contents('/etc/SuSE-release'))) { + $release = strtolower($release); + foreach (array( + 'opensuse' => 'opensuse', + 'sles' => 'suse linux enterprise server', + 'sled' => 'suse linux enterprise desktop' + ) as $distro => $name) { + if (false !== strpos($release, $name)) { + return $distro; + } + } + return $reliable < 2 ? 'suse' : 'linux'; + } + + if ($reliable < 1) { + if (false === ($procVersion = @file_get_contents('/proc/version'))) { + throw new IcingaException('Failed at file_get_contents(/proc/version)'); + } + $procVersion = strtolower($procVersion); + foreach (array( + 'redhat' => 'red hat', + 'suse' => 'suse linux', + 'ubuntu' => 'ubuntu', + 'debian' => 'debian' + ) as $distro => $name) { + if (false !== strpos($procVersion, $name)) { + return $distro; + } + } + } + + return 'linux'; + } + /** * Test of CLI environment * From 5aa9738b55b01ae3a04eaf4b3e7e4846ec35c6fb Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 27 May 2015 10:44:52 +0200 Subject: [PATCH 002/116] Translate no permission for configuration error message refs #6339 --- application/controllers/ConfigController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/controllers/ConfigController.php b/application/controllers/ConfigController.php index 77b5b66d9..8df6620c2 100644 --- a/application/controllers/ConfigController.php +++ b/application/controllers/ConfigController.php @@ -85,7 +85,7 @@ class ConfigController extends Controller public function indexAction() { if ($this->firstAllowedAction === null) { - throw new SecurityException('No permission for configuration'); + throw new SecurityException($this->translate('No permission for configuration')); } $action = $this->getTabs()->get($this->firstAllowedAction); if (substr($action->getUrl()->getPath(), 0, 7) === 'config/') { From d8097d4a160bf416f1beb1a5eec33b796247eea1 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 27 May 2015 10:46:20 +0200 Subject: [PATCH 003/116] Add missing return argument when removing an auth backend fails --- application/controllers/ConfigController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/controllers/ConfigController.php b/application/controllers/ConfigController.php index 8df6620c2..0606f8661 100644 --- a/application/controllers/ConfigController.php +++ b/application/controllers/ConfigController.php @@ -271,7 +271,7 @@ class ConfigController extends Controller $configForm->remove($authBackend); } catch (InvalidArgumentException $e) { Notification::error($e->getMessage()); - return; + return false; } if ($configForm->save()) { From ee5cc63fbca492370a6817a12a831d461d6f5717 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 27 May 2015 10:48:27 +0200 Subject: [PATCH 004/116] Add missing return argument when removing a resource fails --- application/controllers/ConfigController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/controllers/ConfigController.php b/application/controllers/ConfigController.php index 0606f8661..5df2be008 100644 --- a/application/controllers/ConfigController.php +++ b/application/controllers/ConfigController.php @@ -352,7 +352,7 @@ class ConfigController extends Controller $configForm->remove($resource); } catch (InvalidArgumentException $e) { Notification::error($e->getMessage()); - return; + return false; } if ($configForm->save()) { From ba8d7e0afd67aa5459b9112cb47a4777b1a85477 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 27 May 2015 10:49:34 +0200 Subject: [PATCH 005/116] Use Controller::translate() instead of just t() refs #7330 --- application/controllers/DashboardController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/controllers/DashboardController.php b/application/controllers/DashboardController.php index 325556be0..7e4ac479b 100644 --- a/application/controllers/DashboardController.php +++ b/application/controllers/DashboardController.php @@ -78,7 +78,7 @@ class DashboardController extends ActionController $dashboard = $this->dashboard; $form = new DashletForm(); $form->setDashboard($dashboard); - $form->setSubmitLabel(t('Update Dashlet')); + $form->setSubmitLabel($this->translate('Update Dashlet')); if (! $this->_request->getParam('pane')) { throw new Zend_Controller_Action_Exception( 'Missing parameter "pane"', From 49f89ee505cc977c439ccea457cffd200f6737e6 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 27 May 2015 10:59:35 +0200 Subject: [PATCH 006/116] Add missing return argument in the ResourceConfigForm --- application/forms/Config/ResourceConfigForm.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/forms/Config/ResourceConfigForm.php b/application/forms/Config/ResourceConfigForm.php index 5d1eeaa9b..bfec65938 100644 --- a/application/forms/Config/ResourceConfigForm.php +++ b/application/forms/Config/ResourceConfigForm.php @@ -149,7 +149,7 @@ class ResourceConfigForm extends ConfigForm } } catch (InvalidArgumentException $e) { Notification::error($e->getMessage()); - return; + return false; } if ($this->save()) { From e025f59807626dea79a637a7e512a2a5973da9db Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 27 May 2015 11:05:06 +0200 Subject: [PATCH 007/116] Don't provide helpful error messages for unauthenticated users fixes #6952 --- application/controllers/ErrorController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/controllers/ErrorController.php b/application/controllers/ErrorController.php index 0223a66b7..1c01b1672 100644 --- a/application/controllers/ErrorController.php +++ b/application/controllers/ErrorController.php @@ -37,7 +37,7 @@ class ErrorController extends ActionController $path = array_shift($path); $this->getResponse()->setHttpResponseCode(404); $this->view->message = $this->translate('Page not found.'); - if ($modules->hasInstalled($path) && ! $modules->hasEnabled($path)) { + if ($this->Auth()->isAuthenticated() && $modules->hasInstalled($path) && ! $modules->hasEnabled($path)) { $this->view->message .= ' ' . sprintf( $this->translate('Enabling the "%s" module might help!'), $path From 38cecdb724af28ce0e7d44a7b6829a187be5a0ad Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 27 May 2015 11:39:13 +0200 Subject: [PATCH 008/116] Setup Wizard: show how to add a system group "icingaweb2" and how to add the webserver's user to it refs #8705 --- .../views/scripts/form/setup-welcome.phtml | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/modules/setup/application/views/scripts/form/setup-welcome.phtml b/modules/setup/application/views/scripts/form/setup-welcome.phtml index 1b37f60f0..9a4c631fa 100644 --- a/modules/setup/application/views/scripts/form/setup-welcome.phtml +++ b/modules/setup/application/views/scripts/form/setup-welcome.phtml @@ -10,6 +10,39 @@ $configDir = Icinga::app()->getConfigDir(); $setupTokenPath = rtrim($configDir, '/') . '/setup.token'; $cliPath = realpath(Icinga::app()->getApplicationDir() . '/../bin/icingacli'); +$groupadd = null; +$usermod = null; +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; + } + } + + foreach (array( + 'usermod -a -G icingaweb2 apache' => array( + 'redhat', 'rhel', 'centos', 'fedora' + ), + 'usermod -A icingaweb2 wwwrun' => array( + 'suse', 'sles', 'sled', 'opensuse' + ), + 'usermod -a -G icingaweb2 www-data' => array( + 'debian', 'ubuntu' + ) + ) as $usermod_ => $distros) { + if (in_array($distro, $distros)) { + $usermod = $usermod_; + break; + } + } +} ?>

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

@@ -51,6 +84,12 @@ $cliPath = realpath(Icinga::app()->getApplicationDir() . '/../bin/icingacli');
  • translate('Your webserver\'s user is a member of the system group "icingaweb2"'); ?>
  • + +
    + escape($groupadd . ';') ?> + escape($usermod . ';') ?> +
    +

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

    setup config directory --group icingaweb2; From e06225fcf16eaf28a5d98120113ed5efb7d2b53c Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 27 May 2015 11:45:00 +0200 Subject: [PATCH 009/116] monitoring: Fix that searching for specific columns is no longer possible if default search columns are set --- library/Icinga/Web/Widget/FilterEditor.php | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/library/Icinga/Web/Widget/FilterEditor.php b/library/Icinga/Web/Widget/FilterEditor.php index 2d64e4c2b..dd84a5e52 100644 --- a/library/Icinga/Web/Widget/FilterEditor.php +++ b/library/Icinga/Web/Widget/FilterEditor.php @@ -216,25 +216,22 @@ class FilterEditor extends AbstractWidget $filter = $this->getFilter(); if ($search !== null) { - if (empty($this->searchColumns)) { - if (strpos($search, '=') === false) { - Notification::error(mt('monitoring', 'Cannot search here')); - return $this; - } else { - list($k, $v) = preg_split('/=/', $search); - $filter = $this->mergeRootExpression($filter, trim($k), '=', ltrim($v)); - } - } else { - if (false === $this->resetSearchColumns($filter)) { + if (strpos($search, '=') !== false) { + list($k, $v) = preg_split('/=/', $search); + $filter = $this->mergeRootExpression($filter, trim($k), '=', ltrim($v)); + } elseif (! empty($this->searchColumns)) { + if (! $this->resetSearchColumns($filter)) { $filter = Filter::matchAll(); } - $filters = array(); $search = ltrim($search); foreach ($this->searchColumns as $searchColumn) { $filters[] = Filter::expression($searchColumn, '=', "*$search*"); } $filter->andFilter(new FilterOr($filters)); + } else { + Notification::error(mt('monitoring', 'Cannot search here')); + return $this; } $url = $this->url()->setQueryString( From 2833f9edcc4896e501ae991bbf549c2cbe722d98 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Thu, 28 May 2015 09:44:01 +0200 Subject: [PATCH 010/116] Disable all form controls on submit to prevent resubmission fixes #7151 --- public/js/icinga/events.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/js/icinga/events.js b/public/js/icinga/events.js index cc927feb6..84354c960 100644 --- a/public/js/icinga/events.js +++ b/public/js/icinga/events.js @@ -274,6 +274,10 @@ } } + // Disable all form controls to prevent resubmission except for our search input + // Note that disabled form inputs will not be enabled via JavaScript again + $form.find(':input:not(#search):not(:disabled)').prop('disabled', true); + icinga.loader.loadUrl(url, $target, data, method); return false; From 344e17a6053ce036f7828b850c86cd89a90144fb Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Thu, 28 May 2015 10:33:13 +0200 Subject: [PATCH 011/116] monitoring: Fix command links when showing multiple selected hosts fixes #9327 --- .../monitoring/application/controllers/HostsController.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/monitoring/application/controllers/HostsController.php b/modules/monitoring/application/controllers/HostsController.php index 20ae71e6b..8f88330aa 100644 --- a/modules/monitoring/application/controllers/HostsController.php +++ b/modules/monitoring/application/controllers/HostsController.php @@ -131,13 +131,12 @@ class Monitoring_HostsController extends Controller $this->view->objects = $this->hostList; $this->view->unhandledObjects = $this->hostList->getUnhandledObjects(); $this->view->problemObjects = $this->hostList->getProblemObjects(); - $this->view->acknowledgeUnhandledLink = Url::fromPath('monitoring/hosts/acknowledge-problem') - ->setQueryString($this->hostList->getUnhandledObjects()->objectsFilter()); + ->setQueryString($this->hostList->getUnhandledObjects()->objectsFilter()->toQueryString()); $this->view->downtimeUnhandledLink = Url::fromPath('monitoring/hosts/schedule-downtime') - ->setQueryString($this->hostList->getUnhandledObjects()->objectsFilter()); + ->setQueryString($this->hostList->getUnhandledObjects()->objectsFilter()->toQueryString()); $this->view->downtimeLink = Url::fromPath('monitoring/hosts/schedule-downtime') - ->setQueryString($this->hostList->getProblemObjects()->objectsFilter()); + ->setQueryString($this->hostList->getProblemObjects()->objectsFilter()->toQueryString()); $this->view->acknowledgedObjects = $this->hostList->getAcknowledgedObjects(); $this->view->objectsInDowntime = $this->hostList->getObjectsInDowntime(); $this->view->inDowntimeLink = Url::fromPath('monitoring/list/hosts') From 8dc79a388aa092d7893b60b47eb89e817895e025 Mon Sep 17 00:00:00 2001 From: Alexander Fuhr Date: Thu, 28 May 2015 10:42:18 +0200 Subject: [PATCH 012/116] Update resource documentation with the ssh resource configuration refs #7595 --- doc/resources.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/doc/resources.md b/doc/resources.md index 29a35c30d..70255a201 100644 --- a/doc/resources.md +++ b/doc/resources.md @@ -8,7 +8,7 @@ different files, when the information about a data source changes. Each section in **config/resources.ini** represents a data source with the section name being the identifier used to reference this specific data source. Depending on the data source type, the sections define different directives. -The available data source types are *db*, *ldap* and *livestatus* which will described in detail in the following +The available data source types are *db*, *ldap*, *ssh* and *livestatus* which will described in detail in the following paragraphs. ### Database @@ -64,6 +64,26 @@ bind_dn = "cn=admin,ou=people,dc=icinga,dc=org" bind_pw = admin` ```` +### SSH + +A SSH resource contains the information about the user and the private key location, which can be used for the key-based +ssh authentication. + +Directive | Description +--------------------|------------ +**type** | `ssh` +**user** | The username to use when connecting to the server. +**identity_key** | The path to the private key of the user. + +**Example:** + +```` +[ssh] +type = "ssh" +user = "ssh-user" +identity_key = "/etc/icingaweb2/ssh/ssh-user" +```` + ### Livestatus A Livestatus resource represents the location of a Livestatus socket which is used for fetching monitoring data. From 9fdf2a215cbaf640abf69306f35dc92667df6eea Mon Sep 17 00:00:00 2001 From: Alexander Fuhr Date: Thu, 28 May 2015 10:43:30 +0200 Subject: [PATCH 013/116] Update instance documentation with the usage of a ssh resource refs #7595 --- modules/monitoring/doc/instances.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/modules/monitoring/doc/instances.md b/modules/monitoring/doc/instances.md index dd04ea30c..6c139cd05 100644 --- a/modules/monitoring/doc/instances.md +++ b/modules/monitoring/doc/instances.md @@ -3,7 +3,8 @@ ## Abstract The instance.ini defines how icingaweb accesses the command pipe of your icinga process in order to submit external -commands. When you are at the root of your icingaweb installation you can find it under ./config/modules/monitoring/instances.ini. +commands. Depending on the config path (default: /etc/icingaweb2) of your icingaweb installation you can find it +under ./modules/monitoring/instances.ini. ## Syntax @@ -33,5 +34,22 @@ setup key authentication at the endpoint and allow your icingweb's user to acces port=22 ; the port to use (22 if none is given) user=jdoe ; the user to authenticate with +You can also make use of the ssh resource for accessing an icinga pipe with key-based authentication, which will give +you the possibility to define the location of the private key for a specific user, let's have a look: + + [icinga] + path=/usr/local/icinga/var/rw/icinga.cmd ; the path on the remote machine where the icinga.cmd can be found + host=my.remote.machine.com ; the hostname or address of the remote machine + port=22 ; the port to use (22 if none is given) + resource=ssh ; the ssh resource which contains the username and the location of the private key + +And the associated ssh resource: + + [ssh] + type = "ssh" + user = "ssh-user" + identity_key = "/etc/icingaweb2/ssh/ssh-user" + + From bf21f556568c0e96079ba51488ce6e1b4b117d8e Mon Sep 17 00:00:00 2001 From: Alexander Fuhr Date: Thu, 28 May 2015 10:44:58 +0200 Subject: [PATCH 014/116] Add a new ssh resource form refs #7595 --- .../forms/Config/Resource/SshResourceForm.php | 147 ++++++++++++++++++ library/Icinga/Util/File.php | 2 +- 2 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 application/forms/Config/Resource/SshResourceForm.php diff --git a/application/forms/Config/Resource/SshResourceForm.php b/application/forms/Config/Resource/SshResourceForm.php new file mode 100644 index 000000000..ebebada34 --- /dev/null +++ b/application/forms/Config/Resource/SshResourceForm.php @@ -0,0 +1,147 @@ +setName('form_config_resource_ssh'); + } + + /** + * @see Form::createElements() + */ + public function createElements(array $formData) + { + $this->addElement( + 'text', + 'name', + array( + 'required' => true, + 'label' => $this->translate('Resource Name'), + 'description' => $this->translate('The unique name of this resource') + ) + ); + $this->addElement( + 'text', + 'user', + array( + 'required' => true, + 'label' => $this->translate('User'), + 'description' => $this->translate( + 'User to log in as on the remote Icinga instance. Please note that key-based SSH login must be' + . ' possible for this user' + ) + ) + ); + + if ($this->getRequest()->getActionName() != 'editresource') { + + $callbackValidator = new Zend_Validate_Callback(function ($value) { + if (openssl_pkey_get_private($value) === false) { + return false; + } + return true; + }); + $callbackValidator->setMessage( + $this->translate('The given SSH key is invalid'), + Zend_Validate_Callback::INVALID_VALUE + ); + + $this->addElement( + 'textarea', + 'identity_key', + array( + 'required' => true, + 'label' => $this->translate('Private Key'), + 'description' => $this->translate('The private key which will be used for the SSH connections'), + 'class' => 'resource ssh-identity', + 'validators' => array($callbackValidator) + ) + ); + } else { + $resourceName = $formData['name']; + $this->addElement( + 'note', + 'identity_key_note', + array( + 'escape' => false, + 'label' => $this->translate('Private Key'), + 'value' => sprintf( + '%3$s', + $this->getView()->url('config/removeresource', array('resource' => $resourceName)), + sprintf($this->translate( + 'Remove the %s resource' + ), $resourceName), + $this->translate('To modify the private key you must recreate this resource.') + ) + ) + ); + } + + return $this; + } + + /** + * Remove the assigned key to the resource + * + * @param ConfigObject $config + * + * @return bool + */ + public static function beforeRemove(ConfigObject $config) + { + $file = $config->identity_key; + + if (file_exists($file)) { + unlink($file); + return true; + } + return false; + } + + /** + * Creates the assigned key to the resource + * + * @param ResourceConfigForm $form + * + * @return bool + */ + public static function beforeAdd(ResourceConfigForm $form) + { + $configDir = Icinga::app()->getConfigDir(); + $user = $form->getElement('user')->getValue(); + + $filePath = $configDir . '/ssh/' . $user; + + if (! file_exists($filePath)) { + $file = File::create($filePath, 0600); + } else { + $form->error( + sprintf($form->translate('The private key for the user "%s" is already exists.'), $user) + ); + return false; + } + + $file->fwrite($form->getElement('identity_key')->getValue()); + + $form->getElement('identity_key')->setValue($configDir . '/ssh/' . $user); + + return true; + } +} diff --git a/library/Icinga/Util/File.php b/library/Icinga/Util/File.php index 8dd436137..58104e2cc 100644 --- a/library/Icinga/Util/File.php +++ b/library/Icinga/Util/File.php @@ -63,7 +63,7 @@ class File extends SplFileObject throw new NotWritableError(sprintf('Path "%s" is not writable', $dirPath)); } - $file = new static($path, 'x'); + $file = new static($path, 'x+'); if (! @chmod($path, $accessMode)) { $error = error_get_last(); From b2e61a2ebde015f8ff5c5219b50498785375ea98 Mon Sep 17 00:00:00 2001 From: Alexander Fuhr Date: Thu, 28 May 2015 10:45:57 +0200 Subject: [PATCH 015/116] Add resource teaxtarea css styling for ssh-identity refs #7595 --- public/css/icinga/forms.less | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/public/css/icinga/forms.less b/public/css/icinga/forms.less index c8b5df161..f1483b600 100644 --- a/public/css/icinga/forms.less +++ b/public/css/icinga/forms.less @@ -215,6 +215,13 @@ textarea { height: 4em; } +textarea.resource { + &.ssh-identity { + width: 50%; + height: 25em; + } +} + form .description { font-size: 0.8em; margin: 0.3em 0 0 0.6em; From 558120e23bf82d152d512b3207f89e626d6addab Mon Sep 17 00:00:00 2001 From: Alexander Fuhr Date: Thu, 28 May 2015 10:50:13 +0200 Subject: [PATCH 016/116] Add resource ssh usage to resources and instances refs #7595 --- .../forms/Config/ResourceConfigForm.php | 19 ++- .../Config/Instance/RemoteInstanceForm.php | 115 ++++++++++++++++-- .../forms/Config/InstanceConfigForm.php | 5 + 3 files changed, 125 insertions(+), 14 deletions(-) diff --git a/application/forms/Config/ResourceConfigForm.php b/application/forms/Config/ResourceConfigForm.php index 5d1eeaa9b..92ee63476 100644 --- a/application/forms/Config/ResourceConfigForm.php +++ b/application/forms/Config/ResourceConfigForm.php @@ -10,6 +10,7 @@ use Icinga\Forms\Config\Resource\DbResourceForm; use Icinga\Forms\Config\Resource\FileResourceForm; use Icinga\Forms\Config\Resource\LdapResourceForm; use Icinga\Forms\Config\Resource\LivestatusResourceForm; +use Icinga\Forms\Config\Resource\SshResourceForm; use Icinga\Application\Platform; use Icinga\Exception\ConfigurationError; @@ -41,6 +42,8 @@ class ResourceConfigForm extends ConfigForm return new LivestatusResourceForm(); } elseif ($type === 'file') { return new FileResourceForm(); + } elseif ($type === 'ssh') { + return new SshResourceForm(); } else { throw new InvalidArgumentException(sprintf($this->translate('Invalid resource type "%s" provided'), $type)); } @@ -55,7 +58,7 @@ class ResourceConfigForm extends ConfigForm * * @return $this * - * @thrwos InvalidArgumentException In case the resource does already exist + * @throws InvalidArgumentException In case the resource does already exist */ public function add(array $values) { @@ -116,6 +119,11 @@ class ResourceConfigForm extends ConfigForm } $resourceConfig = $this->config->getSection($name); + $resourceForm = $this->getResourceForm($resourceConfig->type); + if (method_exists($resourceForm, 'beforeRemove')) { + $resourceForm::beforeRemove($resourceConfig); + } + $this->config->removeSection($name); return $resourceConfig; } @@ -130,8 +138,9 @@ class ResourceConfigForm extends ConfigForm */ public function onSuccess() { + $resourceForm = $this->getResourceForm($this->getElement('type')->getValue()); + 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)) { $this->addElement($this->getForceCreationCheckbox()); return false; @@ -141,6 +150,11 @@ class ResourceConfigForm extends ConfigForm $resource = $this->request->getQuery('resource'); try { if ($resource === null) { // create new resource + if (method_exists($resourceForm, 'beforeAdd')) { + if (! $resourceForm::beforeAdd($this)) { + return false; + } + } $this->add($this->getValues()); $message = $this->translate('Resource "%s" has been successfully created'); } else { // edit existing resource @@ -212,6 +226,7 @@ class ResourceConfigForm extends ConfigForm $resourceTypes = array( 'file' => $this->translate('File'), 'livestatus' => 'Livestatus', + 'ssh' => $this->translate('SSH Identity'), ); if ($resourceType === 'ldap' || Platform::extensionLoaded('ldap')) { $resourceTypes['ldap'] = 'LDAP'; diff --git a/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php b/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php index 8a1a34b40..244bf66d1 100644 --- a/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php +++ b/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php @@ -3,10 +3,19 @@ namespace Icinga\Module\Monitoring\Forms\Config\Instance; +use Icinga\Data\ResourceFactory; +use Icinga\Exception\ConfigurationError; use Icinga\Web\Form; class RemoteInstanceForm extends Form { + /** + * The available monitoring instance resources split by type + * + * @var array + */ + protected $resources; + /** * (non-PHPDoc) * @see Form::init() For the method documentation. @@ -16,12 +25,89 @@ class RemoteInstanceForm extends Form $this->setName('form_config_monitoring_instance_remote'); } + /** + * Load all available ssh identity resources + * + * @return $this + * + * @throws \Icinga\Exception\ConfigurationError + */ + public function loadResources() + { + $resourceConfig = ResourceFactory::getResourceConfigs(); + + $resources = array(); + foreach ($resourceConfig as $name => $resource) { + if ($resource->type === 'ssh') { + $resources['ssh'][$name] = $name; + } + } + + if (empty($resources)) { + throw new ConfigurationError($this->translate('Could not find any valid monitoring instance resources')); + } + + $this->resources = $resources; + + return $this; + } + /** * (non-PHPDoc) * @see Form::createElements() For the method documentation. */ public function createElements(array $formData = array()) { + $useResource = isset($formData['use_resource']) ? $formData['use_resource'] : $this->getValue('use_resource'); + + $this->addElement( + 'checkbox', + 'use_resource', + array( + 'label' => $this->translate('Use SSH Identity'), + 'description' => $this->translate('Make use of the ssh identity resource'), + 'autosubmit' => true, + 'ignore' => true + ) + ); + + if ($useResource) { + + $this->loadResources(); + + $decorators = static::$defaultElementDecorators; + array_pop($decorators); // Removes the HtmlTag decorator + + $this->addElement( + 'select', + 'resource', + array( + 'required' => true, + 'label' => $this->translate('SSH Identity'), + 'description' => $this->translate('The resource to use'), + 'decorators' => $decorators, + 'multiOptions' => $this->resources['ssh'], + 'value' => current($this->resources['ssh']), + 'autosubmit' => false + ) + ); + $resourceName = isset($formData['resource']) ? $formData['resource'] : $this->getValue('resource'); + $this->addElement( + 'note', + 'resource_note', + array( + 'escape' => false, + 'decorators' => $decorators, + 'value' => sprintf( + '%3$s', + $this->getView()->url('config/editresource', array('resource' => $resourceName)), + sprintf($this->translate('Show the configuration of the %s resource'), $resourceName), + $this->translate('Show resource configuration') + ) + ) + ); + } + $this->addElements(array( array( 'text', @@ -43,8 +129,11 @@ class RemoteInstanceForm extends Form 'description' => $this->translate('SSH port to connect to on the remote Icinga instance'), 'value' => 22 ) - ), - array( + ) + )); + + if (! $useResource) { + $this->addElement( 'text', 'user', array( @@ -55,18 +144,20 @@ class RemoteInstanceForm extends Form . ' possible for this user' ) ) - ), + ); + } + + $this->addElement( + 'text', + 'path', array( - 'text', - 'path', - array( - 'required' => true, - 'label' => $this->translate('Command File'), - 'value' => '/var/run/icinga2/cmd/icinga2.cmd', - 'description' => $this->translate('Path to the Icinga command file on the remote Icinga instance') - ) + 'required' => true, + 'label' => $this->translate('Command File'), + 'value' => '/var/run/icinga2/cmd/icinga2.cmd', + 'description' => $this->translate('Path to the Icinga command file on the remote Icinga instance') ) - )); + ); + return $this; } } diff --git a/modules/monitoring/application/forms/Config/InstanceConfigForm.php b/modules/monitoring/application/forms/Config/InstanceConfigForm.php index b2baea89c..c44e480ac 100644 --- a/modules/monitoring/application/forms/Config/InstanceConfigForm.php +++ b/modules/monitoring/application/forms/Config/InstanceConfigForm.php @@ -143,6 +143,11 @@ class InstanceConfigForm extends ConfigForm $instanceConfig = $this->config->getSection($instanceName)->toArray(); $instanceConfig['name'] = $instanceName; + + if (isset($instanceConfig['resource'])) { + $instanceConfig['use_resource'] = true; + } + $this->populate($instanceConfig); } } From a47d05a0381bb0c025de02f346412ac40b71a055 Mon Sep 17 00:00:00 2001 From: Alexander Fuhr Date: Thu, 28 May 2015 10:51:56 +0200 Subject: [PATCH 017/116] Add identity key usage for a specific user in remote command refs #7595 fixes #7447 --- .../Command/Transport/RemoteCommandFile.php | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php b/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php index 866497a5a..4bae550a8 100644 --- a/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php +++ b/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php @@ -4,6 +4,7 @@ namespace Icinga\Module\Monitoring\Command\Transport; use Icinga\Application\Logger; +use Icinga\Data\ResourceFactory; use Icinga\Exception\ConfigurationError; use Icinga\Module\Monitoring\Command\Exception\TransportException; use Icinga\Module\Monitoring\Command\IcingaCommand; @@ -44,6 +45,13 @@ class RemoteCommandFile implements CommandTransportInterface */ protected $user; + /** + * Path to the identity (private key) file for the key-based authentication + * + * @var string + */ + protected $identityKey; + /** * Path to the Icinga command file on the remote host * @@ -137,6 +145,55 @@ class RemoteCommandFile implements CommandTransportInterface return $this->user; } + /** + * Set the path to the identity file + * + * @param string $identityKey + * + * @return $this + */ + public function setIdentityKey($identityKey) + { + $this->identityKey = (string) $identityKey; + return $this; + } + + /** + * Get the path to the identity path + * + * @return string + */ + public function getIdentityKey() + { + return $this->identityKey; + } + + /** + * Use a given resource to set the user and the key + * + * @param string + * + * @throws ConfigurationError + */ + public function setResource($resource = null) + { + $config = ResourceFactory::getResourceConfig($resource); + + if (! isset($config->user)) { + throw new ConfigurationError( + t("Can't send external Icinga Command. Remote user is missing") + ); + } + if (! isset($config->identity_key)) { + throw new ConfigurationError( + t("Can't send external Icinga Command. The identity key for the remote user is missing") + ); + } + + $this->setUser($config->user); + $this->setIdentityKey($config->identity_key); + } + /** * Set the path to the Icinga command file on the remote host * @@ -192,6 +249,9 @@ class RemoteCommandFile implements CommandTransportInterface if (isset($this->user)) { $ssh .= sprintf(' -l %s', escapeshellarg($this->user)); } + if (isset($this->identityKey)) { + $ssh .= sprintf(' -o StrictHostKeyChecking=no -i %s', escapeshellarg($this->identityKey)); + } $ssh .= sprintf( ' %s "echo %s > %s" 2>&1', // Redirect stderr to stdout escapeshellarg($this->host), From 646cffd62d615e6a4d716440a9f5728c19b87120 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Thu, 28 May 2015 12:22:57 +0200 Subject: [PATCH 018/116] monitoring: Remove the services tab when showing host or service details refs #7998 --- .../Web/Controller/MonitoredObjectController.php | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php b/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php index 9d27b1268..7ac509d7b 100644 --- a/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php +++ b/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php @@ -181,19 +181,6 @@ abstract class MonitoredObjectController extends Controller ) ); } - $tabs->add( - 'services', - array( - 'title' => sprintf( - $this->translate('List all services on host %s'), - $isService ? $object->getHost()->getName() : $object->getName() - ), - 'label' => $this->translate('Services'), - 'icon' => 'services', - 'url' => 'monitoring/show/services', - 'urlParams' => $params - ) - ); if ($this->backend->hasQuery('eventHistory')) { $tabs->add( 'history', From b3e0851b62d079c49d486b542c7845e662e88068 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Thu, 28 May 2015 12:24:00 +0200 Subject: [PATCH 019/116] monitoring: Remove the services tab when showing an object's history refs #7998 --- .../application/controllers/ShowController.php | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/modules/monitoring/application/controllers/ShowController.php b/modules/monitoring/application/controllers/ShowController.php index a8a4583d1..c26bb460f 100644 --- a/modules/monitoring/application/controllers/ShowController.php +++ b/modules/monitoring/application/controllers/ShowController.php @@ -228,19 +228,6 @@ class Monitoring_ShowController extends Controller ) ); } - $tabs->add( - 'services', - array( - 'title' => sprintf( - $this->translate('List all services on host %s'), - $isService ? $object->getHost()->getName() : $object->getName() - ), - 'label' => $this->translate('Services'), - 'icon' => 'services', - 'url' => 'monitoring/show/services', - 'urlParams' => $params, - ) - ); if ($this->backend->hasQuery('eventHistory')) { $tabs->add( 'history', From 8dbb215e27e199e8522ab597a355d5c6a247b9cf Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Thu, 28 May 2015 12:25:31 +0200 Subject: [PATCH 020/116] monitoring: Remove services action from the ShowController refs #7998 --- .../application/controllers/ShowController.php | 14 -------------- .../application/views/scripts/show/services.phtml | 8 -------- 2 files changed, 22 deletions(-) delete mode 100644 modules/monitoring/application/views/scripts/show/services.phtml diff --git a/modules/monitoring/application/controllers/ShowController.php b/modules/monitoring/application/controllers/ShowController.php index c26bb460f..538a75132 100644 --- a/modules/monitoring/application/controllers/ShowController.php +++ b/modules/monitoring/application/controllers/ShowController.php @@ -78,20 +78,6 @@ class Monitoring_ShowController extends Controller $this->setupPaginationControl($this->view->history, 50); } - public function servicesAction() - { - $this->setAutorefreshInterval(15); - $this->getTabs()->activate('services'); - $this->_setParam('service', ''); - // TODO: This used to be a hack and still is. Modifying query string here. - $_SERVER['QUERY_STRING'] = (string) $this->params->without('service')->set('limit', ''); - $this->view->services = $this->view->action('services', 'list', 'monitoring', array( - 'view' => 'compact', - 'sort' => 'service_description', - )); - $this->fetchHostStats(); - } - protected function fetchHostStats() { $this->view->stats = $this->backend->select()->from('statusSummary', array( diff --git a/modules/monitoring/application/views/scripts/show/services.phtml b/modules/monitoring/application/views/scripts/show/services.phtml deleted file mode 100644 index d3a0c3ef8..000000000 --- a/modules/monitoring/application/views/scripts/show/services.phtml +++ /dev/null @@ -1,8 +0,0 @@ -
    - compact): ?> - tabs; ?> - -render('partials/host/object-header.phtml') ?> -render('partials/host/servicesummary.phtml') ?> -
    - From e0fe6440d242398da3f5ea984b949facb2e1059e Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Thu, 28 May 2015 12:26:22 +0200 Subject: [PATCH 021/116] monitoring: Show host's services in the next container refs #7998 --- .../partials/host/servicesummary.phtml | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml b/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml index 04408808f..a851b7255 100644 --- a/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml +++ b/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml @@ -12,7 +12,7 @@ function urlAddFilterOptional($url, $filter, $optional) { return $url->setQueryString($f->toQueryString()); } -$selfUrl = Url::fromPath('monitoring/show/services', array('host' => $object->host_name)); +$selfUrl = Url::fromPath('monitoring/list/services', array('host' => $object->host_name)); $currentUrl = Url::fromRequest()->without('limit')->getRelativeUrl(); ?>
    compact ? ' data-base-target="col1"' : ''; ?>> stats->services_total): ?> @@ -27,15 +27,18 @@ $currentUrl = Url::fromRequest()->without('limit')->getRelativeUrl(); ), $selfUrl, null, - array('title' => sprintf( - $this->translatePlural( - 'List all %u service on host %s', - 'List all %u services on host %s', - $object->stats->services_total + array( + 'title' => sprintf( + $this->translatePlural( + 'List all %u service on host %s', + 'List all %u services on host %s', + $object->stats->services_total + ), + $object->stats->services_total, + $object->host_name ), - $object->stats->services_total, - $object->host_name - )) + 'data-base-target' => '_next' + ) ); ?> translate('No services configured on this host'); ?> @@ -151,4 +154,4 @@ foreach (array(2 => 'critical', 3 => 'unknown', 1 => 'warning') as $stateId => $ -
    \ No newline at end of file +
    From 49589128ef0b23a86cf932ee8d7e29598b89b4c8 Mon Sep 17 00:00:00 2001 From: Alexander Fuhr Date: Thu, 28 May 2015 14:09:13 +0200 Subject: [PATCH 022/116] Rename identity_key with private_key in ssh resource refs #7595 --- .../forms/Config/Resource/SshResourceForm.php | 10 +++---- .../Command/Transport/RemoteCommandFile.php | 28 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/application/forms/Config/Resource/SshResourceForm.php b/application/forms/Config/Resource/SshResourceForm.php index ebebada34..c5c49d1e0 100644 --- a/application/forms/Config/Resource/SshResourceForm.php +++ b/application/forms/Config/Resource/SshResourceForm.php @@ -65,7 +65,7 @@ class SshResourceForm extends Form $this->addElement( 'textarea', - 'identity_key', + 'private_key', array( 'required' => true, 'label' => $this->translate('Private Key'), @@ -78,7 +78,7 @@ class SshResourceForm extends Form $resourceName = $formData['name']; $this->addElement( 'note', - 'identity_key_note', + 'private_key_note', array( 'escape' => false, 'label' => $this->translate('Private Key'), @@ -106,7 +106,7 @@ class SshResourceForm extends Form */ public static function beforeRemove(ConfigObject $config) { - $file = $config->identity_key; + $file = $config->private_key; if (file_exists($file)) { unlink($file); @@ -138,9 +138,9 @@ class SshResourceForm extends Form return false; } - $file->fwrite($form->getElement('identity_key')->getValue()); + $file->fwrite($form->getElement('private_key')->getValue()); - $form->getElement('identity_key')->setValue($configDir . '/ssh/' . $user); + $form->getElement('private_key')->setValue($configDir . '/ssh/' . $user); return true; } diff --git a/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php b/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php index 4bae550a8..14902cbbf 100644 --- a/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php +++ b/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php @@ -46,11 +46,11 @@ class RemoteCommandFile implements CommandTransportInterface protected $user; /** - * Path to the identity (private key) file for the key-based authentication + * Path to the private key file for the key-based authentication * * @var string */ - protected $identityKey; + protected $privateKey; /** * Path to the Icinga command file on the remote host @@ -146,26 +146,26 @@ class RemoteCommandFile implements CommandTransportInterface } /** - * Set the path to the identity file + * Set the path to the private key file * - * @param string $identityKey + * @param string $privateKey * * @return $this */ - public function setIdentityKey($identityKey) + public function setPrivateKey($privateKey) { - $this->identityKey = (string) $identityKey; + $this->privateKey = (string) $privateKey; return $this; } /** - * Get the path to the identity path + * Get the path to the private key * * @return string */ - public function getIdentityKey() + public function getPrivateKey() { - return $this->identityKey; + return $this->privateKey; } /** @@ -184,14 +184,14 @@ class RemoteCommandFile implements CommandTransportInterface t("Can't send external Icinga Command. Remote user is missing") ); } - if (! isset($config->identity_key)) { + if (! isset($config->private_key)) { throw new ConfigurationError( - t("Can't send external Icinga Command. The identity key for the remote user is missing") + t("Can't send external Icinga Command. The private key for the remote user is missing") ); } $this->setUser($config->user); - $this->setIdentityKey($config->identity_key); + $this->setPrivateKey($config->private_key); } /** @@ -249,8 +249,8 @@ class RemoteCommandFile implements CommandTransportInterface if (isset($this->user)) { $ssh .= sprintf(' -l %s', escapeshellarg($this->user)); } - if (isset($this->identityKey)) { - $ssh .= sprintf(' -o StrictHostKeyChecking=no -i %s', escapeshellarg($this->identityKey)); + if (isset($this->privateKey)) { + $ssh .= sprintf(' -o StrictHostKeyChecking=no -i %s', escapeshellarg($this->privateKey)); } $ssh .= sprintf( ' %s "echo %s > %s" 2>&1', // Redirect stderr to stdout From f3a37e957542a615bbe8026d27a8cae5847a9f34 Mon Sep 17 00:00:00 2001 From: Alexander Fuhr Date: Thu, 28 May 2015 14:15:01 +0200 Subject: [PATCH 023/116] Update documentation for resources and instances I've just only renamed the identity_key to the private_key thats it refs #7595 --- doc/resources.md | 4 ++-- modules/monitoring/doc/instances.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/resources.md b/doc/resources.md index 70255a201..19cb84a57 100644 --- a/doc/resources.md +++ b/doc/resources.md @@ -73,7 +73,7 @@ Directive | Description --------------------|------------ **type** | `ssh` **user** | The username to use when connecting to the server. -**identity_key** | The path to the private key of the user. +**private_key** | The path to the private key of the user. **Example:** @@ -81,7 +81,7 @@ Directive | Description [ssh] type = "ssh" user = "ssh-user" -identity_key = "/etc/icingaweb2/ssh/ssh-user" +private_key = "/etc/icingaweb2/ssh/ssh-user" ```` ### Livestatus diff --git a/modules/monitoring/doc/instances.md b/modules/monitoring/doc/instances.md index 6c139cd05..ea8b3f5b8 100644 --- a/modules/monitoring/doc/instances.md +++ b/modules/monitoring/doc/instances.md @@ -48,7 +48,7 @@ And the associated ssh resource: [ssh] type = "ssh" user = "ssh-user" - identity_key = "/etc/icingaweb2/ssh/ssh-user" + private_key = "/etc/icingaweb2/ssh/ssh-user" From fa1a5c609d4148ff6c25d05939edfdcef2af7245 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Mon, 1 Jun 2015 17:21:11 +0200 Subject: [PATCH 024/116] monitoring: Open links of a host's service summary in the next container refs #7998 --- .../partials/host/servicesummary.phtml | 80 +++++++++++-------- 1 file changed, 46 insertions(+), 34 deletions(-) diff --git a/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml b/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml index a851b7255..dd111b27b 100644 --- a/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml +++ b/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml @@ -51,15 +51,18 @@ $currentUrl = Url::fromRequest()->without('limit')->getRelativeUrl(); $object->stats->services_ok, $selfUrl, array('service_state' => 0), - array('title' => sprintf( - $this->translatePlural( - 'List %u service that is currently in state OK on host %s', - 'List %u services which are currently in state OK on host %s', - $object->stats->services_ok + array( + 'title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state OK on host %s', + 'List %u services which are currently in state OK on host %s', + $object->stats->services_ok + ), + $object->stats->services_ok, + $object->host_name ), - $object->stats->services_ok, - $object->host_name - )) + 'data-base-target' => '_next' + ) ); ?> @@ -90,16 +93,19 @@ foreach (array(2 => 'critical', 3 => 'unknown', 1 => 'warning') as $stateId => $ $object->stats->$unhandled, $selfUrl, $paramsUnhandled, - array('title' => sprintf( - $this->translatePlural( - 'List %u service that is currently in state %s on host %s', - 'List %u services which are currently in state %s on host %s', - $object->stats->$unhandled + array( + 'title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state %s on host %s', + 'List %u services which are currently in state %s on host %s', + $object->stats->$unhandled + ), + $object->stats->$unhandled, + Service::getStateText($stateId, true), + $object->host_name ), - $object->stats->$unhandled, - Service::getStateText($stateId, true), - $object->host_name - )) + 'data-base-target' => '_next' + ) ); } if ($object->stats->$handled) { @@ -116,16 +122,19 @@ foreach (array(2 => 'critical', 3 => 'unknown', 1 => 'warning') as $stateId => $ $object->stats->$handled, $selfUrl, $paramsHandled, - array('title' => sprintf( - $this->translatePlural( - 'List %u service that is currently in state %s (Acknowledged) on host %s', - 'List %u services which are currently in state %s (Acknowledged) on host %s', - $object->stats->$handled + array( + 'title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state %s (Acknowledged) on host %s', + 'List %u services which are currently in state %s (Acknowledged) on host %s', + $object->stats->$handled + ), + $object->stats->$handled, + Service::getStateText($stateId, true), + $object->host_name ), - $object->stats->$handled, - Service::getStateText($stateId, true), - $object->host_name - )) + 'data-base-target' => '_next' + ) ); if ($object->stats->$unhandled) { echo "\n"; @@ -141,15 +150,18 @@ foreach (array(2 => 'critical', 3 => 'unknown', 1 => 'warning') as $stateId => $ $object->stats->services_pending, $selfUrl, array('service_state' => 99), - array('title' => sprintf( - $this->translatePlural( - 'List %u service that is currently in state PENDING on host %s', - 'List %u services which are currently in state PENDING on host %s', - $object->stats->services_pending + array( + 'title' => sprintf( + $this->translatePlural( + 'List %u service that is currently in state PENDING on host %s', + 'List %u services which are currently in state PENDING on host %s', + $object->stats->services_pending + ), + $object->stats->services_pending, + $object->host_name ), - $object->stats->services_pending, - $object->host_name - )) + 'data-base-target' => '_next' + ) ) ?> From 0ce2227385f5fc1531a5a8338adb730b986720b2 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Tue, 2 Jun 2015 10:06:54 +0200 Subject: [PATCH 025/116] monitoring: Don't add class active to followed links of a host's service summary The links now open in the next container. Managing active state of followed links is not yet possible. refs #7998 --- .../partials/host/servicesummary.phtml | 27 +++---------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml b/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml index dd111b27b..b69682e1b 100644 --- a/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml +++ b/modules/monitoring/application/views/scripts/partials/host/servicesummary.phtml @@ -13,7 +13,6 @@ function urlAddFilterOptional($url, $filter, $optional) { } $selfUrl = Url::fromPath('monitoring/list/services', array('host' => $object->host_name)); -$currentUrl = Url::fromRequest()->without('limit')->getRelativeUrl(); ?>
    compact ? ' data-base-target="col1"' : ''; ?>> stats->services_total): ?> qlink( @@ -46,7 +45,7 @@ $currentUrl = Url::fromRequest()->without('limit')->getRelativeUrl(); stats->services_ok): ?> - + qlink( $object->stats->services_ok, $selfUrl, @@ -74,19 +73,7 @@ foreach (array(2 => 'critical', 3 => 'unknown', 1 => 'warning') as $stateId => $ $unhandled = $pre . '_unhandled'; $paramsHandled = array('service_state' => $stateId, 'service_handled' => 1); $paramsUnhandled = array('service_state' => $stateId, 'service_handled' => 0); - if ($object->stats->$unhandled) { - $compareUrl = $selfUrl->with($paramsUnhandled)->getRelativeUrl(); - } else { - $compareUrl = $selfUrl->with($paramsHandled)->getRelativeUrl(); - } - - if ($compareUrl === $currentUrl) { - $active = ' active'; - } else { - $active = ''; - } - - echo ''; + echo ''; if ($object->stats->$unhandled) { echo $this->qlink( @@ -109,14 +96,8 @@ foreach (array(2 => 'critical', 3 => 'unknown', 1 => 'warning') as $stateId => $ ); } if ($object->stats->$handled) { - - if ($selfUrl->with($paramsHandled)->getRelativeUrl() === $currentUrl) { - $active = ' active'; - } else { - $active = ''; - } if ($object->stats->$unhandled) { - echo ''; + echo ''; } echo $this->qlink( $object->stats->$handled, @@ -145,7 +126,7 @@ foreach (array(2 => 'critical', 3 => 'unknown', 1 => 'warning') as $stateId => $ } ?> stats->services_pending): ?> - + qlink( $object->stats->services_pending, $selfUrl, From c01512d5e94558f3ce897de57fb16ea367c979dd Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Tue, 2 Jun 2015 10:07:13 +0200 Subject: [PATCH 026/116] monitoring: Fix link to unhandled services of a host refs #7998 --- modules/monitoring/application/views/scripts/list/hosts.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/monitoring/application/views/scripts/list/hosts.phtml b/modules/monitoring/application/views/scripts/list/hosts.phtml index 5535d14fa..f8b77574d 100644 --- a/modules/monitoring/application/views/scripts/list/hosts.phtml +++ b/modules/monitoring/application/views/scripts/list/hosts.phtml @@ -69,7 +69,7 @@ if (count($hosts) === 0) { $this->translatePlural('%u unhandled service', '%u unhandled services', $host->host_unhandled_services), $host->host_unhandled_services ), - 'monitoring/show/services', + 'monitoring/list/services', array( 'host' => $host->host_name, 'service_problem' => 1, From 2da45d2e942f74d1b32389fe19474977ea2d9ae2 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Tue, 2 Jun 2015 10:07:43 +0200 Subject: [PATCH 027/116] monitoring: Fix link to a host's services in the service grid refs #7998 --- .../monitoring/application/views/scripts/list/servicegrid.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/monitoring/application/views/scripts/list/servicegrid.phtml b/modules/monitoring/application/views/scripts/list/servicegrid.phtml index a9cbac445..73fbfe3b5 100644 --- a/modules/monitoring/application/views/scripts/list/servicegrid.phtml +++ b/modules/monitoring/application/views/scripts/list/servicegrid.phtml @@ -67,7 +67,7 @@ foreach ($serviceDescriptions as $service_description): ?> qlink( $host_name, - 'monitoring/show/services?' . $serviceFilter, + 'monitoring/list/services?' . $serviceFilter, array('host' => $host_name), array('title' => sprintf($this->translate('List all reported services on host %s'), $host_name)) ); ?> From 441fc336437c2ed9ae2e44f6ebc5e9928e20897b Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 2 Jun 2015 11:53:43 +0200 Subject: [PATCH 028/116] Include version information on `git archive' refs #9247 --- .gitattributes | 3 +++ application/VERSION | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 application/VERSION diff --git a/.gitattributes b/.gitattributes index a05c2f5cd..6ae8ee7af 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,3 +6,6 @@ Vagrantfile export-ignore # Normalize puppet manifests' line endings to LF on checkin and prevent conversion to CRLF when the files are checked out .puppet* eol=lf + +# Include version information on `git archive' +/application/VERSION export-subst diff --git a/application/VERSION b/application/VERSION new file mode 100644 index 000000000..47f3a662f --- /dev/null +++ b/application/VERSION @@ -0,0 +1,2 @@ +GitCommitID: $Format:%H%d$ +GitCommitDate: $Format:%ci$ From 887bd0b8f04b76a57b27b7a46cb8333c5e5d1755 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 2 Jun 2015 16:43:32 +0200 Subject: [PATCH 029/116] Show Icinga Web 2's version in the frontend refs #9247 --- application/controllers/AboutController.php | 91 +++++++++++++++++++++ application/views/scripts/about/index.phtml | 20 +++++ 2 files changed, 111 insertions(+) create mode 100644 application/controllers/AboutController.php create mode 100644 application/views/scripts/about/index.phtml diff --git a/application/controllers/AboutController.php b/application/controllers/AboutController.php new file mode 100644 index 000000000..6572d7e8b --- /dev/null +++ b/application/controllers/AboutController.php @@ -0,0 +1,91 @@ +view->appVersion = null; + $this->view->gitCommitID = null; + $this->view->gitCommitDate = null; + + if (false !== ($appVersion = @file( + Icinga::app()->getApplicationDir() . DIRECTORY_SEPARATOR . 'VERSION', + FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES + ))) { + foreach ($appVersion as $av) { + $matches = array(); + if (false === ($res = preg_match( + '/(?view->gitCommitID !== null) { + break; + } + + $matches2 = array(); + if (false === ($res = preg_match( + '/(?view->gitCommitID = $matches2[1]; + if (! isset($matches2[2])) { + break; + } + + foreach (preg_split( + '/\s*,\s*/', $matches2[2], -1, PREG_SPLIT_NO_EMPTY + ) as $refName) { + $matches3 = array(); + if (false === ($res = preg_match( + '/(?view->appVersion = $matches3[1]; + break; + } + } + break; + case 'GitCommitDate': + if ($this->view->gitCommitDate !== null) { + break; + } + + $matches2 = array(); + if (false === ($res = preg_match( + '/(?view->gitCommitDate = $matches2[1]; + } + } + } + } + } +} diff --git a/application/views/scripts/about/index.phtml b/application/views/scripts/about/index.phtml new file mode 100644 index 000000000..0429a5374 --- /dev/null +++ b/application/views/scripts/about/index.phtml @@ -0,0 +1,20 @@ +
    +

    Icinga Web 2

    + translate('Version: %s'), $appVersion), + array($this->translate('Git commit ID: %s'), $gitCommitID), + array($this->translate('Git commit date: %s'), $gitCommitDate) + ) as $version) { + list($label, $value) = $version; + if ($value !== null) { + $versionInfo[] = sprintf($label, htmlspecialchars($value)); + } + } + ?> +

    translate('unknown version') + : nl2br(implode("\n", $versionInfo), false) + ?>

    +
    From 5b8de49cdf46ad6499f33a90252e5822cb83fc82 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 2 Jun 2015 17:16:27 +0200 Subject: [PATCH 030/116] Link to the about page refs #9247 --- library/Icinga/Web/Menu.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/Icinga/Web/Menu.php b/library/Icinga/Web/Menu.php index 63d92fc9f..45dd2dd9c 100644 --- a/library/Icinga/Web/Menu.php +++ b/library/Icinga/Web/Menu.php @@ -279,6 +279,11 @@ class Menu implements RecursiveIterator 'priority' => 990, 'renderer' => 'ForeignMenuItemRenderer' )); + + $this->add(t('About'), array( + 'url' => 'about', + 'priority' => 1000 + )); } } From e0c0e9c8743ce4b9a1d364c75a96be5fbdfc2da3 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 3 Jun 2015 14:36:46 +0200 Subject: [PATCH 031/116] LdapUserBackend: Move function retrieveGeneralizedTime into its parent refs #7343 --- .../Authentication/User/LdapUserBackend.php | 32 ------------------- library/Icinga/Repository/Repository.php | 31 ++++++++++++++++++ 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/library/Icinga/Authentication/User/LdapUserBackend.php b/library/Icinga/Authentication/User/LdapUserBackend.php index 154a33a44..64ce0ac24 100644 --- a/library/Icinga/Authentication/User/LdapUserBackend.php +++ b/library/Icinga/Authentication/User/LdapUserBackend.php @@ -4,7 +4,6 @@ namespace Icinga\Authentication\User; use DateTime; -use Icinga\Application\Logger; use Icinga\Data\ConfigObject; use Icinga\Exception\AuthenticationException; use Icinga\Exception\ProgrammingError; @@ -325,37 +324,6 @@ class LdapUserBackend extends Repository implements UserBackendInterface return ((int) $value & $ADS_UF_ACCOUNTDISABLE) === 0; } - /** - * Parse the given value based on the ASN.1 standard (GeneralizedTime) and return its timestamp representation - * - * @param string|null $value - * - * @return int - */ - protected function retrieveGeneralizedTime($value) - { - if ($value === null) { - return $value; - } - - if ( - ($dateTime = DateTime::createFromFormat('YmdHis.uO', $value)) !== false - || ($dateTime = DateTime::createFromFormat('YmdHis.uZ', $value)) !== false - || ($dateTime = DateTime::createFromFormat('YmdHis.u', $value)) !== false - || ($dateTime = DateTime::createFromFormat('YmdHis', $value)) !== false - || ($dateTime = DateTime::createFromFormat('YmdHi', $value)) !== false - || ($dateTime = DateTime::createFromFormat('YmdH', $value)) !== false - ) { - return $dateTime->getTimeStamp(); - } else { - Logger::debug(sprintf( - 'Failed to parse "%s" based on the ASN.1 standard (GeneralizedTime) for user backend "%s".', - $value, - $this->getName() - )); - } - } - /** * Return whether the given shadowExpire value defines that a user is permitted to login * diff --git a/library/Icinga/Repository/Repository.php b/library/Icinga/Repository/Repository.php index 803f4958f..98b3e0ab4 100644 --- a/library/Icinga/Repository/Repository.php +++ b/library/Icinga/Repository/Repository.php @@ -599,6 +599,37 @@ abstract class Repository implements Selectable return $value; } + /** + * Parse the given value based on the ASN.1 standard (GeneralizedTime) and return its timestamp representation + * + * @param string|null $value + * + * @return int + */ + protected function retrieveGeneralizedTime($value) + { + if ($value === null) { + return $value; + } + + if ( + ($dateTime = DateTime::createFromFormat('YmdHis.uO', $value)) !== false + || ($dateTime = DateTime::createFromFormat('YmdHis.uZ', $value)) !== false + || ($dateTime = DateTime::createFromFormat('YmdHis.u', $value)) !== false + || ($dateTime = DateTime::createFromFormat('YmdHis', $value)) !== false + || ($dateTime = DateTime::createFromFormat('YmdHi', $value)) !== false + || ($dateTime = DateTime::createFromFormat('YmdH', $value)) !== false + ) { + return $dateTime->getTimeStamp(); + } else { + Logger::debug(sprintf( + 'Failed to parse "%s" based on the ASN.1 standard (GeneralizedTime) in repository "%s".', + $value, + $this->getName() + )); + } + } + /** * Validate that the requested table exists * From 96f5f8fd4950e40bbe5121c435cf7bda9b1838a6 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 3 Jun 2015 15:16:54 +0200 Subject: [PATCH 032/116] LdapUserBackend: Do not fetch a user's groups refs #7343 --- .../Authentication/User/LdapUserBackend.php | 58 +------------------ .../Authentication/User/UserBackend.php | 12 ---- 2 files changed, 1 insertion(+), 69 deletions(-) diff --git a/library/Icinga/Authentication/User/LdapUserBackend.php b/library/Icinga/Authentication/User/LdapUserBackend.php index 64ce0ac24..e85a39029 100644 --- a/library/Icinga/Authentication/User/LdapUserBackend.php +++ b/library/Icinga/Authentication/User/LdapUserBackend.php @@ -64,8 +64,6 @@ class LdapUserBackend extends Repository implements UserBackendInterface ) ); - protected $groupOptions; - /** * Normed attribute names based on known LDAP environments * @@ -178,17 +176,6 @@ class LdapUserBackend extends Repository implements UserBackendInterface return $this->filter; } - public function setGroupOptions(array $options) - { - $this->groupOptions = $options; - return $this; - } - - public function getGroupOptions() - { - return $this->groupOptions; - } - /** * Return the given attribute name normed to known LDAP enviroments, if possible * @@ -381,41 +368,6 @@ class LdapUserBackend extends Repository implements UserBackendInterface } } - /** - * Retrieve the user groups - * - * @TODO: Subject to change, see #7343 - * - * @param string $dn - * - * @return array - */ - public function getGroups($dn) - { - if (empty($this->groupOptions) || ! isset($this->groupOptions['group_base_dn'])) { - return array(); - } - - $result = $this->ds->select() - ->setBase($this->groupOptions['group_base_dn']) - ->from( - $this->groupOptions['group_class'], - array($this->groupOptions['group_attribute']) - ) - ->where( - $this->groupOptions['group_member_attribute'], - $dn - ) - ->fetchAll(); - - $groups = array(); - foreach ($result as $group) { - $groups[] = $group->{$this->groupOptions['group_attribute']}; - } - - return $groups; - } - /** * Authenticate the given user * @@ -440,15 +392,7 @@ class LdapUserBackend extends Repository implements UserBackendInterface return false; } - $authenticated = $this->ds->testCredentials($userDn, $password); - if ($authenticated) { - $groups = $this->getGroups($userDn); - if ($groups !== null) { - $user->setGroups($groups); - } - } - - return $authenticated; + return $this->ds->testCredentials($userDn, $password); } catch (LdapException $e) { throw new AuthenticationException( 'Failed to authenticate user "%s" against backend "%s". An exception was thrown:', diff --git a/library/Icinga/Authentication/User/UserBackend.php b/library/Icinga/Authentication/User/UserBackend.php index 3d11289fb..42a4c2b42 100644 --- a/library/Icinga/Authentication/User/UserBackend.php +++ b/library/Icinga/Authentication/User/UserBackend.php @@ -165,12 +165,6 @@ class UserBackend $backend->setUserClass($backendConfig->get('user_class', 'user')); $backend->setUserNameAttribute($backendConfig->get('user_name_attribute', 'sAMAccountName')); $backend->setFilter($backendConfig->filter); - $backend->setGroupOptions(array( - 'group_base_dn' => $backendConfig->get('group_base_dn', $resource->getDN()), - 'group_attribute' => $backendConfig->get('group_attribute', 'sAMAccountName'), - 'group_member_attribute' => $backendConfig->get('group_member_attribute', 'member'), - 'group_class' => $backendConfig->get('group_class', 'group') - )); break; case 'ldap': $backend = new LdapUserBackend($resource); @@ -178,12 +172,6 @@ class UserBackend $backend->setUserClass($backendConfig->get('user_class', 'inetOrgPerson')); $backend->setUserNameAttribute($backendConfig->get('user_name_attribute', 'uid')); $backend->setFilter($backendConfig->filter); - $backend->setGroupOptions(array( - 'group_base_dn' => $backendConfig->group_base_dn, - 'group_attribute' => $backendConfig->group_attribute, - 'group_member_attribute' => $backendConfig->group_member_attribute, - 'group_class' => $backendConfig->group_class - )); break; } From 757e993871f4409678bd3064d1bd40eb18cec3e6 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 3 Jun 2015 14:40:58 +0200 Subject: [PATCH 033/116] Outsource version-getting logic to Version::get() refs #9247 --- application/controllers/AboutController.php | 80 +-------------- application/views/scripts/about/index.phtml | 17 ++-- library/Icinga/Version.php | 105 ++++++++++++++++++++ 3 files changed, 116 insertions(+), 86 deletions(-) create mode 100644 library/Icinga/Version.php diff --git a/application/controllers/AboutController.php b/application/controllers/AboutController.php index 6572d7e8b..376490443 100644 --- a/application/controllers/AboutController.php +++ b/application/controllers/AboutController.php @@ -4,88 +4,12 @@ # namespace Icinga\Application\Controllers; use Icinga\Web\Controller\ActionController; -use Icinga\Application\Icinga; -use Icinga\Exception\IcingaException; +use Icinga\Version; class AboutController extends ActionController { public function indexAction() { - $this->view->appVersion = null; - $this->view->gitCommitID = null; - $this->view->gitCommitDate = null; - - if (false !== ($appVersion = @file( - Icinga::app()->getApplicationDir() . DIRECTORY_SEPARATOR . 'VERSION', - FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES - ))) { - foreach ($appVersion as $av) { - $matches = array(); - if (false === ($res = preg_match( - '/(?view->gitCommitID !== null) { - break; - } - - $matches2 = array(); - if (false === ($res = preg_match( - '/(?view->gitCommitID = $matches2[1]; - if (! isset($matches2[2])) { - break; - } - - foreach (preg_split( - '/\s*,\s*/', $matches2[2], -1, PREG_SPLIT_NO_EMPTY - ) as $refName) { - $matches3 = array(); - if (false === ($res = preg_match( - '/(?view->appVersion = $matches3[1]; - break; - } - } - break; - case 'GitCommitDate': - if ($this->view->gitCommitDate !== null) { - break; - } - - $matches2 = array(); - if (false === ($res = preg_match( - '/(?view->gitCommitDate = $matches2[1]; - } - } - } - } + $this->view->version = Version::get(); } } diff --git a/application/views/scripts/about/index.phtml b/application/views/scripts/about/index.phtml index 0429a5374..3c37a5e73 100644 --- a/application/views/scripts/about/index.phtml +++ b/application/views/scripts/about/index.phtml @@ -2,14 +2,15 @@

    Icinga Web 2

    translate('Version: %s'), $appVersion), - array($this->translate('Git commit ID: %s'), $gitCommitID), - array($this->translate('Git commit date: %s'), $gitCommitDate) - ) as $version) { - list($label, $value) = $version; - if ($value !== null) { - $versionInfo[] = sprintf($label, htmlspecialchars($value)); + if ($version !== false) { + foreach (array( + 'appVersion' => $this->translate('Version: %s'), + 'gitCommitID' => $this->translate('Git commit ID: %s'), + 'gitCommitDate' => $this->translate('Git commit date: %s') + ) as $key => $label) { + if (null !== ($value = $version[$key])) { + $versionInfo[] = sprintf($label, htmlspecialchars($value)); + } } } ?> diff --git a/library/Icinga/Version.php b/library/Icinga/Version.php new file mode 100644 index 000000000..130e75060 --- /dev/null +++ b/library/Icinga/Version.php @@ -0,0 +1,105 @@ + null, + 'gitCommitID' => null, + 'gitCommitDate' => null + ); + + if (false !== ($appVersion = @file( + Icinga::app()->getApplicationDir() . DIRECTORY_SEPARATOR . 'VERSION', + FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES + ))) { + foreach ($appVersion as $av) { + $matches = array(); + if (false === ($res = preg_match( + '/(? Date: Wed, 3 Jun 2015 15:28:07 +0200 Subject: [PATCH 034/116] Introduce class LdapRepository refs #7343 --- .../Authentication/User/LdapUserBackend.php | 33 +--------- library/Icinga/Repository/LdapRepository.php | 65 +++++++++++++++++++ 2 files changed, 67 insertions(+), 31 deletions(-) create mode 100644 library/Icinga/Repository/LdapRepository.php diff --git a/library/Icinga/Authentication/User/LdapUserBackend.php b/library/Icinga/Authentication/User/LdapUserBackend.php index e85a39029..333969891 100644 --- a/library/Icinga/Authentication/User/LdapUserBackend.php +++ b/library/Icinga/Authentication/User/LdapUserBackend.php @@ -7,13 +7,13 @@ use DateTime; use Icinga\Data\ConfigObject; use Icinga\Exception\AuthenticationException; use Icinga\Exception\ProgrammingError; -use Icinga\Repository\Repository; +use Icinga\Repository\LdapRepository; use Icinga\Repository\RepositoryQuery; use Icinga\Protocol\Ldap\Exception as LdapException; use Icinga\Protocol\Ldap\Expression; use Icinga\User; -class LdapUserBackend extends Repository implements UserBackendInterface +class LdapUserBackend extends LdapRepository implements UserBackendInterface { /** * The base DN to use for a query @@ -64,18 +64,6 @@ class LdapUserBackend extends Repository implements UserBackendInterface ) ); - /** - * Normed attribute names based on known LDAP environments - * - * @var array - */ - protected $normedAttributes = array( - 'uid' => 'uid', - 'user' => 'user', - 'inetorgperson' => 'inetOrgPerson', - 'samaccountname' => 'sAMAccountName' - ); - /** * Set the base DN to use for a query * @@ -176,23 +164,6 @@ class LdapUserBackend extends Repository implements UserBackendInterface return $this->filter; } - /** - * Return the given attribute name normed to known LDAP enviroments, if possible - * - * @param string $name - * - * @return string - */ - protected function getNormedAttribute($name) - { - $loweredName = strtolower($name); - if (array_key_exists($loweredName, $this->normedAttributes)) { - return $this->normedAttributes[$loweredName]; - } - - return $name; - } - /** * Apply the given configuration to this backend * diff --git a/library/Icinga/Repository/LdapRepository.php b/library/Icinga/Repository/LdapRepository.php new file mode 100644 index 000000000..15594d269 --- /dev/null +++ b/library/Icinga/Repository/LdapRepository.php @@ -0,0 +1,65 @@ + + *
  • Attribute name normalization
  • + * + */ +abstract class LdapRepository extends Repository +{ + /** + * The datasource being used + * + * @var Connection + */ + protected $ds; + + /** + * Normed attribute names based on known LDAP environments + * + * @var array + */ + protected $normedAttributes = array( + 'uid' => 'uid', + 'user' => 'user', + 'group' => 'group', + 'member' => 'member', + 'inetorgperson' => 'inetOrgPerson', + 'samaccountname' => 'sAMAccountName' + ); + + /** + * Create a new LDAP repository object + * + * @param Connection $ds The data source to use + */ + public function __construct(Connection $ds) + { + parent::__construct($ds); + } + + /** + * Return the given attribute name normed to known LDAP enviroments, if possible + * + * @param string $name + * + * @return string + */ + protected function getNormedAttribute($name) + { + $loweredName = strtolower($name); + if (array_key_exists($loweredName, $this->normedAttributes)) { + return $this->normedAttributes[$loweredName]; + } + + return $name; + } +} \ No newline at end of file From e65cf954e69ec750f0ebe89eff3a4c5f144170b1 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 3 Jun 2015 16:08:29 +0200 Subject: [PATCH 035/116] LdapRepository: Add normed attribute `gid' refs #7343 --- library/Icinga/Repository/LdapRepository.php | 1 + 1 file changed, 1 insertion(+) diff --git a/library/Icinga/Repository/LdapRepository.php b/library/Icinga/Repository/LdapRepository.php index 15594d269..7cf00ae66 100644 --- a/library/Icinga/Repository/LdapRepository.php +++ b/library/Icinga/Repository/LdapRepository.php @@ -29,6 +29,7 @@ abstract class LdapRepository extends Repository */ protected $normedAttributes = array( 'uid' => 'uid', + 'gid' => 'gid', 'user' => 'user', 'group' => 'group', 'member' => 'member', From 89d992278baf8e4fcd56255746f77a8f2850fdd9 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 3 Jun 2015 16:27:50 +0200 Subject: [PATCH 036/116] Introduce class LdapUserGroupBackend refs #743 --- .../UserGroup/LdapUserGroupBackend.php | 376 ++++++++++++++++++ .../UserGroup/UserGroupBackend.php | 24 ++ 2 files changed, 400 insertions(+) create mode 100644 library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php new file mode 100644 index 000000000..1e6addba2 --- /dev/null +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -0,0 +1,376 @@ + array( + 'order' => 'asc' + ) + ); + + /** + * Set the base DN to use for a user query + * + * @param string $baseDn + * + * @return $this + */ + public function setUserBaseDn($baseDn) + { + if (($baseDn = trim($baseDn))) { + $this->userBaseDn = $baseDn; + } + + return $this; + } + + /** + * Return the base DN to use for a user query + * + * @return string + */ + public function getUserBaseDn() + { + return $this->userBaseDn; + } + + /** + * Set the base DN to use for a group query + * + * @param string $baseDn + * + * @return $this + */ + public function setGroupBaseDn($baseDn) + { + if (($baseDn = trim($baseDn))) { + $this->groupBaseDn = $baseDn; + } + + return $this; + } + + /** + * Return the base DN to use for a group query + * + * @return string + */ + public function getGroupBaseDn() + { + return $this->groupBaseDn; + } + + /** + * Set the objectClass where to look for users + * + * @param string $userClass + * + * @return $this + */ + public function setUserClass($userClass) + { + $this->userClass = $this->getNormedAttribute($userClass); + return $this; + } + + /** + * Return the objectClass where to look for users + * + * @return string + */ + public function getUserClass() + { + return $this->userClass; + } + + /** + * Set the objectClass where to look for groups + * + * Sets also the base table name for the underlying repository. + * + * @param string $groupClass + * + * @return $this + */ + public function setGroupClass($groupClass) + { + $this->baseTable = $this->groupClass = $this->getNormedAttribute($groupClass); + return $this; + } + + /** + * Return the objectClass where to look for groups + * + * @return string + */ + public function getGroupClass() + { + return $this->groupClass; + } + + /** + * Set the attribute name where to find a user's name + * + * @param string $userNameAttribute + * + * @return $this + */ + public function setUserNameAttribute($userNameAttribute) + { + $this->userNameAttribute = $this->getNormedAttribute($userNameAttribute); + return $this; + } + + /** + * Return the attribute name where to find a user's name + * + * @return string + */ + public function getUserNameAttribute() + { + return $this->userNameAttribute; + } + + /** + * Set the attribute name where to find a group's name + * + * @param string $groupNameAttribute + * + * @return $this + */ + public function setGroupNameAttribute($groupNameAttribute) + { + $this->groupNameAttribute = $this->getNormedAttribute($groupNameAttribute); + return $this; + } + + /** + * Return the attribute name where to find a group's name + * + * @return string + */ + public function getGroupNameAttribute() + { + return $this->groupNameAttribute; + } + + /** + * Set the attribute name where to find a group's member + * + * @param string $groupMemberAttribute + * + * @return $this + */ + public function setGroupMemberAttribute($groupMemberAttribute) + { + $this->groupMemberAttribute = $this->getNormedAttribute($groupMemberAttribute); + return $this; + } + + /** + * Return the attribute name where to find a group's member + * + * @return string + */ + public function getGroupMemberAttribute() + { + return $this->groupMemberAttribute; + } + + /** + * Return a new query for the given columns + * + * @param array $columns The desired columns, if null all columns will be queried + * + * @return RepositoryQuery + */ + public function select(array $columns = null) + { + $query = parent::select($columns); + $query->getQuery()->setBase($this->groupBaseDn); + return $query; + } + + /** + * Initialize this repository's query columns + * + * @return array + * + * @throws ProgrammingError In case either $this->groupNameAttribute or $this->groupClass has not been set yet + */ + protected function initializeQueryColumns() + { + if ($this->groupClass === null) { + throw new ProgrammingError('It is required to set the objectClass where to look for groups first'); + } + if ($this->groupNameAttribute === null) { + throw new ProgrammingError('It is required to set a attribute name where to find a group\'s name first'); + } + + if ($this->ds->getCapabilities()->hasAdOid()) { + $createdAtAttribute = 'whenCreated'; + $lastModifiedAttribute = 'whenChanged'; + } else { + $createdAtAttribute = 'createTimestamp'; + $lastModifiedAttribute = 'modifyTimestamp'; + } + + $columns = array( + 'group' => $this->groupNameAttribute, + 'group_name' => $this->groupNameAttribute, + 'user' => $this->groupMemberAttribute, + 'user_name' => $this->groupMemberAttribute, + 'created_at' => $createdAtAttribute, + 'last_modified' => $lastModifiedAttribute + ); + return array('group' => $columns, 'group_membership' => $columns); + } + + /** + * Initialize this repository's conversion rules + * + * @return array + * + * @throws ProgrammingError In case $this->groupClass has not been set yet + */ + protected function initializeConversionRules() + { + if ($this->groupClass === null) { + throw new ProgrammingError('It is required to set the objectClass where to look for groups first'); + } + + return array( + $this->groupClass => array( + 'created_at' => 'generalized_time', + 'last_modified' => 'generalized_time' + ) + ); + } + + /** + * Validate that the requested table exists + * + * This will return $this->groupClass in case $table equals "group" or "group_membership". + * + * @param string $table The table to validate + * @param RepositoryQuery $query An optional query to pass as context + * (unused by the base implementation) + * + * @return string + * + * @throws ProgrammingError In case the given table does not exist + */ + public function requireTable($table, RepositoryQuery $query = null) + { + $table = parent::requireTable($table, $query); + if ($table === 'group' || $table === 'group_membership') { + $table = $this->groupClass; + } + + return $table; + } + + /** + * Return the groups the given user is a member of + * + * @param User $user + * + * @return array + */ + public function getMemberships(User $user) + { + $userDn = $this->ds + ->select() + ->from($this->userClass) + ->where($this->userNameAttribute, $user->getUsername()) + ->setBase($this->userBaseDn) + ->setUsePagedResults(false) + ->fetchDn(); + + if ($userDn === null) { + return array(); + } + + $groupQuery = $this->ds + ->select() + ->from($this->groupClass, array($this->groupNameAttribute)) + ->where($this->groupMemberAttribute, $userDn) + ->setBase($this->groupBaseDn); + + $groups = array(); + foreach ($groupQuery as $row) { + $groups[] = $row->{$this->groupNameAttribute}; + } + + return $groups; + } +} diff --git a/library/Icinga/Authentication/UserGroup/UserGroupBackend.php b/library/Icinga/Authentication/UserGroup/UserGroupBackend.php index dd4900ea8..585f7c449 100644 --- a/library/Icinga/Authentication/UserGroup/UserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/UserGroupBackend.php @@ -21,6 +21,8 @@ class UserGroupBackend */ protected static $defaultBackends = array( 'db', + 'ldap', + 'msldap', //'ini' ); @@ -156,6 +158,28 @@ class UserGroupBackend case 'ini': $backend = new IniUserGroupBackend($resource); break; + case 'ldap': + $backend = new LdapUserGroupBackend($resource); + $backend + ->setGroupBaseDn($backendConfig->base_dn) + ->setUserBaseDn($backendConfig->get('user_base_dn', $backend->getGroupBaseDn())) + ->setGroupClass($backendConfig->get('group_class', 'group')) + ->setUserClass($backendConfig->get('user_class', 'inetOrgPerson')) + ->setGroupNameAttribute($backendConfig->get('group_name_attribute', 'gid')) + ->setUserNameAttribute($backendConfig->get('user_name_attribute', 'uid')) + ->setGroupMemberAttribute($backendConfig->get('group_member_attribute', 'member')); + break; + case 'msldap': + $backend = new LdapUserGroupBackend($resource); + $backend + ->setGroupBaseDn($backendConfig->base_dn) + ->setUserBaseDn($backendConfig->get('user_base_dn', $backend->getGroupBaseDn())) + ->setGroupClass($backendConfig->get('group_class', 'group')) + ->setUserClass($backendConfig->get('user_class', 'user')) + ->setGroupNameAttribute($backendConfig->get('group_name_attribute', 'sAMAccountName')) + ->setUserNameAttribute($backendConfig->get('user_name_attribute', $backend->getGroupNameAttribute())) + ->setGroupMemberAttribute($backendConfig->get('group_member_attribute', 'member')); + break; } $backend->setName($name); From d9eb8f9e8dc8bead46b738fd1aa6270ec7ce8e0e Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 3 Jun 2015 16:33:22 +0200 Subject: [PATCH 037/116] LdapUserGroupBackend: Do not extend LdapRepository Selecting groups works, but not memberships. Does not make sense until both things work... refs #7343 --- .../UserGroup/LdapUserGroupBackend.php | 67 ++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index 1e6addba2..1873cdc8e 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -8,7 +8,7 @@ use Icinga\Repository\LdapRepository; use Icinga\Repository\RepositoryQuery; use Icinga\User; -class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInterface +class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBackendInterface { /** * The base DN to use for a user query @@ -77,6 +77,70 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt ) ); + /** + * Normed attribute names based on known LDAP environments + * + * @var array + */ + protected $normedAttributes = array( + 'uid' => 'uid', + 'gid' => 'gid', + 'user' => 'user', + 'group' => 'group', + 'member' => 'member', + 'inetorgperson' => 'inetOrgPerson', + 'samaccountname' => 'sAMAccountName' + ); + + /** + * The name of this repository + * + * @var string + */ + protected $name; + + /** + * Return the given attribute name normed to known LDAP enviroments, if possible + * + * @param string $name + * + * @return string + */ + protected function getNormedAttribute($name) + { + $loweredName = strtolower($name); + if (array_key_exists($loweredName, $this->normedAttributes)) { + return $this->normedAttributes[$loweredName]; + } + + return $name; + } + + /** + * Set this repository's name + * + * @param string $name + * + * @return $this + */ + public function setName($name) + { + $this->name = $name; + return $this; + } + + /** + * Return this repository's name + * + * In case no name has been explicitly set yet, the class name is returned. + * + * @return string + */ + public function getName() + { + return $this->name; + } + /** * Set the base DN to use for a user query * @@ -284,6 +348,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt $lastModifiedAttribute = 'modifyTimestamp'; } + // TODO(jom): Fetching memberships does not work currently, we'll need some aggregate functionality! $columns = array( 'group' => $this->groupNameAttribute, 'group_name' => $this->groupNameAttribute, From 90d946f149abc57899b4e875e8eedd3b1e66fb38 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 3 Jun 2015 16:40:14 +0200 Subject: [PATCH 038/116] LdapUserGroupBackend: We need a datasource, actually Forgot to add this when disabling LdapRepository inheritance... refs #7343 --- .../UserGroup/LdapUserGroupBackend.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index 1873cdc8e..718006fea 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -99,6 +99,23 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken */ protected $name; + /** + * The datasource being used + * + * @var Connection + */ + protected $ds; + + /** + * Create a new LDAP repository object + * + * @param Connection $ds The data source to use + */ + public function __construct($ds) + { + $this->ds = $ds; + } + /** * Return the given attribute name normed to known LDAP enviroments, if possible * From 7577b37cdb466470373df072ef23cd0d55a0241b Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 3 Jun 2015 15:28:36 +0200 Subject: [PATCH 039/116] Change VERSION's format refs #9247 --- application/VERSION | 3 +- application/views/scripts/about/index.phtml | 2 +- library/Icinga/Version.php | 100 ++++---------------- 3 files changed, 21 insertions(+), 84 deletions(-) diff --git a/application/VERSION b/application/VERSION index 47f3a662f..519b667a7 100644 --- a/application/VERSION +++ b/application/VERSION @@ -1,2 +1 @@ -GitCommitID: $Format:%H%d$ -GitCommitDate: $Format:%ci$ +$Format:%H%d %ci$ diff --git a/application/views/scripts/about/index.phtml b/application/views/scripts/about/index.phtml index 3c37a5e73..53f5a1842 100644 --- a/application/views/scripts/about/index.phtml +++ b/application/views/scripts/about/index.phtml @@ -8,7 +8,7 @@ 'gitCommitID' => $this->translate('Git commit ID: %s'), 'gitCommitDate' => $this->translate('Git commit date: %s') ) as $key => $label) { - if (null !== ($value = $version[$key])) { + if (array_key_exists($key, $version) && null !== ($value = $version[$key])) { $versionInfo[] = sprintf($label, htmlspecialchars($value)); } } diff --git a/library/Icinga/Version.php b/library/Icinga/Version.php index 130e75060..4e5b36945 100644 --- a/library/Icinga/Version.php +++ b/library/Icinga/Version.php @@ -15,91 +15,29 @@ class Version */ public static function get() { - $versionInfo = array( - 'appVersion' => null, - 'gitCommitID' => null, - 'gitCommitDate' => null - ); - - if (false !== ($appVersion = @file( - Icinga::app()->getApplicationDir() . DIRECTORY_SEPARATOR . 'VERSION', - FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES + if (false === ($appVersion = @file_get_contents( + Icinga::app()->getApplicationDir() . DIRECTORY_SEPARATOR . 'VERSION' ))) { - foreach ($appVersion as $av) { - $matches = array(); - if (false === ($res = preg_match( - '/(?\w+)(?:\s*\(.*?(?:(?<=[\(,])\s*tag\s*:\s*v(?P.+?)\s*(?=[\),]).*?)?\))?\s*(?P\S+)/ms', + $appVersion, + $matches + ))) { + throw new IcingaException('Failed at preg_match()'); + } + if ($res === 0) { + return false; } - return $versionInfo; + foreach ($matches as $key => $value) { + if (is_int($key) || $value === '') { + unset($matches[$key]); + } + } + return $matches; } } From 3fd0d99db2e01d6db6faf9c504ed92710fdd008b Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 5 Jun 2015 09:57:40 +0200 Subject: [PATCH 040/116] LdapUserGroupBackend: Add support for custom query filters refs #7343 --- .../UserGroup/LdapUserGroupBackend.php | 85 ++++++++++++++++++- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index 718006fea..c0eaa0cb4 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -4,6 +4,7 @@ namespace Icinga\Authentication\UserGroup; use Icinga\Exception\ProgrammingError; +use Icinga\Protocol\Ldap\Expression; use Icinga\Repository\LdapRepository; use Icinga\Repository\RepositoryQuery; use Icinga\User; @@ -59,6 +60,20 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken */ protected $groupMemberAttribute; + /** + * The custom LDAP filter to apply on a user query + * + * @var string + */ + protected $userFilter; + + /** + * The custom LDAP filter to apply on a group query + * + * @var string + */ + protected $groupFilter; + /** * The columns which are not permitted to be queried * @@ -327,6 +342,58 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken return $this->groupMemberAttribute; } + /** + * Set the custom LDAP filter to apply on a user query + * + * @param string $filter + * + * @return $this + */ + public function setUserFilter($filter) + { + if (($filter = trim($filter))) { + $this->userFilter = $filter; + } + + return $this; + } + + /** + * Return the custom LDAP filter to apply on a user query + * + * @return string + */ + public function getUserFilter() + { + return $this->userFilter; + } + + /** + * Set the custom LDAP filter to apply on a group query + * + * @param string $filter + * + * @return $this + */ + public function setGroupFilter($filter) + { + if (($filter = trim($filter))) { + $this->groupFilter = $filter; + } + + return $this; + } + + /** + * Return the custom LDAP filter to apply on a group query + * + * @return string + */ + public function getGroupFilter() + { + return $this->groupFilter; + } + /** * Return a new query for the given columns * @@ -338,6 +405,11 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken { $query = parent::select($columns); $query->getQuery()->setBase($this->groupBaseDn); + if ($this->groupFilter) { + // TODO(jom): This should differentiate between groups and their memberships + $query->getQuery()->where(new Expression($this->groupFilter)); + } + return $query; } @@ -430,15 +502,17 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken */ public function getMemberships(User $user) { - $userDn = $this->ds + $userQuery = $this->ds ->select() ->from($this->userClass) ->where($this->userNameAttribute, $user->getUsername()) ->setBase($this->userBaseDn) - ->setUsePagedResults(false) - ->fetchDn(); + ->setUsePagedResults(false); + if ($this->userFilter) { + $userQuery->where(new Expression($this->userFilter)); + } - if ($userDn === null) { + if (($userDn = $userQuery->fetchDn()) === null) { return array(); } @@ -447,6 +521,9 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken ->from($this->groupClass, array($this->groupNameAttribute)) ->where($this->groupMemberAttribute, $userDn) ->setBase($this->groupBaseDn); + if ($this->groupFilter) { + $groupQuery->where(new Expression($this->groupFilter)); + } $groups = array(); foreach ($groupQuery as $row) { From 602f0cf7556489675dbf488ca429898415f5f383 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 5 Jun 2015 10:18:24 +0200 Subject: [PATCH 041/116] Move Version to Icinga\Application\ refs #9247 --- application/controllers/AboutController.php | 2 +- library/Icinga/{ => Application}/Version.php | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) rename library/Icinga/{ => Application}/Version.php (95%) diff --git a/application/controllers/AboutController.php b/application/controllers/AboutController.php index 376490443..a4247f234 100644 --- a/application/controllers/AboutController.php +++ b/application/controllers/AboutController.php @@ -4,7 +4,7 @@ # namespace Icinga\Application\Controllers; use Icinga\Web\Controller\ActionController; -use Icinga\Version; +use Icinga\Application\Version; class AboutController extends ActionController { diff --git a/library/Icinga/Version.php b/library/Icinga/Application/Version.php similarity index 95% rename from library/Icinga/Version.php rename to library/Icinga/Application/Version.php index 4e5b36945..0c49330ae 100644 --- a/library/Icinga/Version.php +++ b/library/Icinga/Application/Version.php @@ -1,9 +1,8 @@ Date: Fri, 5 Jun 2015 10:19:28 +0200 Subject: [PATCH 042/116] LdapUserGroupBackend: Let the backend decide which defaults to use refs #7343 --- .../UserGroup/LdapUserGroupBackend.php | 62 +++++++++++++++++++ .../UserGroup/UserGroupBackend.php | 19 +----- 2 files changed, 63 insertions(+), 18 deletions(-) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index c0eaa0cb4..d2387b7b4 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -3,6 +3,7 @@ namespace Icinga\Authentication\UserGroup; +use Icinga\Data\ConfigObject; use Icinga\Exception\ProgrammingError; use Icinga\Protocol\Ldap\Expression; use Icinga\Repository\LdapRepository; @@ -532,4 +533,65 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken return $groups; } + + /** + * Apply the given configuration on this backend + * + * @param ConfigObject $config + * + * @return $this + */ + public function setConfig(ConfigObject $config) + { + if ($config->backend === 'ldap') { + $defaults = $this->getOpenLdapDefaults(); + } elseif ($config->backend === 'msldap') { + $defaults = $this->getActiveDirectoryDefaults(); + } else { + $defaults = new ConfigObject(); + } + + return $this + ->setGroupBaseDn($config->base_dn) + ->setUserBaseDn($config->get('user_base_dn', $this->getGroupBaseDn())) + ->setGroupClass($config->get('group_class', $defaults->group_class)) + ->setUserClass($config->get('user_class', $defaults->user_class)) + ->setGroupNameAttribute($config->get('group_name_attribute', $defaults->group_name_attribute)) + ->setUserNameAttribute($config->get('user_name_attribute', $defaults->user_name_attribute)) + ->setGroupMemberAttribute($config->get('group_member_attribute', $defaults->group_member_attribute)) + ->setGroupFilter($config->filter) + ->setUserFilter($config->user_filter); + } + + /** + * Return the configuration defaults for an OpenLDAP environment + * + * @return ConfigObject + */ + protected function getOpenLdapDefaults() + { + return new ConfigObject(array( + 'group_class' => 'group', + 'user_class' => 'inetOrgPerson', + 'group_name_attribute' => 'gid', + 'user_name_attribute' => 'uid', + 'group_member_attribute' => 'member' + )); + } + + /** + * Return the configuration defaults for an ActiveDirectory environment + * + * @return ConfigObject + */ + protected function getActiveDirectoryDefaults() + { + return new ConfigObject(array( + 'group_class' => 'group', + 'user_class' => 'user', + 'group_name_attribute' => 'sAMAccountName', + 'user_name_attribute' => 'sAMAccountName', + 'group_member_attribute' => 'member' + )); + } } diff --git a/library/Icinga/Authentication/UserGroup/UserGroupBackend.php b/library/Icinga/Authentication/UserGroup/UserGroupBackend.php index 585f7c449..978860a37 100644 --- a/library/Icinga/Authentication/UserGroup/UserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/UserGroupBackend.php @@ -159,26 +159,9 @@ class UserGroupBackend $backend = new IniUserGroupBackend($resource); break; case 'ldap': - $backend = new LdapUserGroupBackend($resource); - $backend - ->setGroupBaseDn($backendConfig->base_dn) - ->setUserBaseDn($backendConfig->get('user_base_dn', $backend->getGroupBaseDn())) - ->setGroupClass($backendConfig->get('group_class', 'group')) - ->setUserClass($backendConfig->get('user_class', 'inetOrgPerson')) - ->setGroupNameAttribute($backendConfig->get('group_name_attribute', 'gid')) - ->setUserNameAttribute($backendConfig->get('user_name_attribute', 'uid')) - ->setGroupMemberAttribute($backendConfig->get('group_member_attribute', 'member')); - break; case 'msldap': $backend = new LdapUserGroupBackend($resource); - $backend - ->setGroupBaseDn($backendConfig->base_dn) - ->setUserBaseDn($backendConfig->get('user_base_dn', $backend->getGroupBaseDn())) - ->setGroupClass($backendConfig->get('group_class', 'group')) - ->setUserClass($backendConfig->get('user_class', 'user')) - ->setGroupNameAttribute($backendConfig->get('group_name_attribute', 'sAMAccountName')) - ->setUserNameAttribute($backendConfig->get('user_name_attribute', $backend->getGroupNameAttribute())) - ->setGroupMemberAttribute($backendConfig->get('group_member_attribute', 'member')); + $backend->setConfig($backendConfig); break; } From cbc731034a20b6f4860ec5e70887f4d98fe5f0a9 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 5 Jun 2015 10:23:17 +0200 Subject: [PATCH 043/116] Don't throw any exception refs #9247 --- library/Icinga/Application/Version.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/library/Icinga/Application/Version.php b/library/Icinga/Application/Version.php index 0c49330ae..65b6138d9 100644 --- a/library/Icinga/Application/Version.php +++ b/library/Icinga/Application/Version.php @@ -3,8 +3,6 @@ namespace Icinga\Application; -use Icinga\Exception\IcingaException; - class Version { /** @@ -25,10 +23,7 @@ class Version '/(?\w+)(?:\s*\(.*?(?:(?<=[\(,])\s*tag\s*:\s*v(?P.+?)\s*(?=[\),]).*?)?\))?\s*(?P\S+)/ms', $appVersion, $matches - ))) { - throw new IcingaException('Failed at preg_match()'); - } - if ($res === 0) { + )) || $res === 0) { return false; } From 127489ca20cc22255024e6c017220fc28f983c84 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 5 Jun 2015 10:40:47 +0200 Subject: [PATCH 044/116] UserBackend: Allow to only pass a backend's name --- library/Icinga/Authentication/User/UserBackend.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Authentication/User/UserBackend.php b/library/Icinga/Authentication/User/UserBackend.php index 42a4c2b42..7afd561a7 100644 --- a/library/Icinga/Authentication/User/UserBackend.php +++ b/library/Icinga/Authentication/User/UserBackend.php @@ -3,6 +3,7 @@ namespace Icinga\Authentication\User; +use Icinga\Application\Config; use Icinga\Application\Logger; use Icinga\Application\Icinga; use Icinga\Data\ConfigObject; @@ -106,8 +107,17 @@ class UserBackend * * @throws ConfigurationError */ - public static function create($name, ConfigObject $backendConfig) + public static function create($name, ConfigObject $backendConfig = null) { + if ($backendConfig === null) { + $authConfig = Config::app('authentication'); + if ($authConfig->hasSection($name)) { + $backendConfig = $authConfig->getSection($name); + } else { + throw new ConfigurationError('User backend "%s" does not exist', $name); + } + } + if ($backendConfig->name !== null) { $name = $backendConfig->name; } From 0ab192cd1f75a90a1e23e913a2819e5429afb602 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 5 Jun 2015 10:41:47 +0200 Subject: [PATCH 045/116] LdapUserGroupBackend: Allow to link a user backend refs #7343 --- .../UserGroup/LdapUserGroupBackend.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index d2387b7b4..42d67a0e8 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -3,7 +3,10 @@ namespace Icinga\Authentication\UserGroup; +use Icinga\Authentication\User\UserBackend; +use Icinga\Authentication\User\LdapUserBackend; use Icinga\Data\ConfigObject; +use Icinga\Exception\ConfigurationError; use Icinga\Exception\ProgrammingError; use Icinga\Protocol\Ldap\Expression; use Icinga\Repository\LdapRepository; @@ -540,6 +543,8 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken * @param ConfigObject $config * * @return $this + * + * @throws ConfigurationError In case a linked user backend does not exist or is not a LdapUserBackend */ public function setConfig(ConfigObject $config) { @@ -551,6 +556,20 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken $defaults = new ConfigObject(); } + if ($config->user_backend) { + $userBackend = UserBackend::create($config->user_backend); + if (! $userBackend instanceof LdapUserBackend) { + throw new ConfigurationError('User backend "%s" is not of type LDAP', $config->user_backend); + } + + $defaults->merge(array( + 'user_base_dn' => $userBackend->getBaseDn(), + 'user_class' => $userBackend->getUserClass(), + 'user_name_attribute' => $userBackend->getUserNameAttribute(), + 'user_filter' => $userBackend->getFilter() + )); + } + return $this ->setGroupBaseDn($config->base_dn) ->setUserBaseDn($config->get('user_base_dn', $this->getGroupBaseDn())) From 02d2ea682e0774cbe072626fd7dd412d19457e6f Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 5 Jun 2015 10:51:54 +0200 Subject: [PATCH 046/116] LdapUserGroupBackend: Do not permit to link different directories I cannot think of a valid usecase right now. In case someone got one, revert this commit and make use of the backend itself and not only its configuration. refs #7343 --- .../UserGroup/LdapUserGroupBackend.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index 42d67a0e8..a51df856d 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -544,7 +544,7 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken * * @return $this * - * @throws ConfigurationError In case a linked user backend does not exist or is not a LdapUserBackend + * @throws ConfigurationError In case a linked user backend does not exist or is invalid */ public function setConfig(ConfigObject $config) { @@ -562,6 +562,17 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken throw new ConfigurationError('User backend "%s" is not of type LDAP', $config->user_backend); } + if ( + $this->ds->getHostname() !== $userBackend->getDataSource()->getHostname() + || $this->ds->getPort() !== $userBackend->getDataSource()->getPort() + ) { + // TODO(jom): Elaborate whether it makes sense to link directories on different hosts + throw new ConfigurationError( + 'It is required that a linked user backend refers to the ' + . 'same directory as it\'s user group backend counterpart' + ); + } + $defaults->merge(array( 'user_base_dn' => $userBackend->getBaseDn(), 'user_class' => $userBackend->getUserClass(), From cacd97fb4665284e325a4ee287152b1bf1b999d6 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 5 Jun 2015 11:09:31 +0200 Subject: [PATCH 047/116] LdapUserGroupBackend: Make default configuration providers public I'd like to access these when preparing a config form. refs #7343 --- .../Icinga/Authentication/UserGroup/LdapUserGroupBackend.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index a51df856d..60b94ec27 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -598,7 +598,7 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken * * @return ConfigObject */ - protected function getOpenLdapDefaults() + public function getOpenLdapDefaults() { return new ConfigObject(array( 'group_class' => 'group', @@ -614,7 +614,7 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken * * @return ConfigObject */ - protected function getActiveDirectoryDefaults() + public function getActiveDirectoryDefaults() { return new ConfigObject(array( 'group_class' => 'group', From 67322e0b364d6ff4d33a35e77ff2890299fbc035 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 5 Jun 2015 11:56:15 +0200 Subject: [PATCH 048/116] Show an error message rather than just "unknown version" refs #9247 --- application/views/scripts/about/index.phtml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/application/views/scripts/about/index.phtml b/application/views/scripts/about/index.phtml index 53f5a1842..6a01e1bf9 100644 --- a/application/views/scripts/about/index.phtml +++ b/application/views/scripts/about/index.phtml @@ -13,9 +13,13 @@ } } } + + echo ( + 0 === count($versionInfo) + ? '

    ' . $this->translate( + 'Can\'t determine Icinga Web 2\'s version' + ) + : '

    ' . nl2br(implode("\n", $versionInfo), false) + ) . '

    '; ?> -

    translate('unknown version') - : nl2br(implode("\n", $versionInfo), false) - ?>

    From 0a5dcd2c8f5340e76c66c058d3bbee0441e27930 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 5 Jun 2015 12:03:23 +0200 Subject: [PATCH 049/116] Include VERSION in the RPM package refs #9247 --- icingaweb2.spec | 1 + 1 file changed, 1 insertion(+) diff --git a/icingaweb2.spec b/icingaweb2.spec index 1ea69451d..c7aa4cb8d 100644 --- a/icingaweb2.spec +++ b/icingaweb2.spec @@ -218,6 +218,7 @@ rm -rf %{buildroot} %{basedir}/application/forms %{basedir}/application/layouts %{basedir}/application/views +%{basedir}/application/VERSION %{basedir}/doc %{basedir}/modules %{basedir}/public From 6b14195809ce51bdf6f1c803af165438e948bd72 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 5 Jun 2015 12:38:55 +0200 Subject: [PATCH 050/116] Rename .fileNotReadable to .message-error refs #9247 --- public/css/icinga/layout-structure.less | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/public/css/icinga/layout-structure.less b/public/css/icinga/layout-structure.less index 3a8713a38..5b5eee5ef 100644 --- a/public/css/icinga/layout-structure.less +++ b/public/css/icinga/layout-structure.less @@ -331,8 +331,7 @@ html { position: absolute; } -/* TODO: replace this with .error */ -.fileNotReadable { +.message-error { padding: 0.5em; background-color: @colorCritical; font-weight: bold; From 7ebf185036a7bf00eeb65e40859634796e7f327c Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 5 Jun 2015 13:07:16 +0200 Subject: [PATCH 051/116] UserGroupBackendForm: Fix that autosubmit do not have any effect refs #7343 --- .../Config/UserGroup/UserGroupBackendForm.php | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/application/forms/Config/UserGroup/UserGroupBackendForm.php b/application/forms/Config/UserGroup/UserGroupBackendForm.php index dd9875c9a..abcf1eaa4 100644 --- a/application/forms/Config/UserGroup/UserGroupBackendForm.php +++ b/application/forms/Config/UserGroup/UserGroupBackendForm.php @@ -13,6 +13,13 @@ use Icinga\Forms\ConfigForm; */ class UserGroupBackendForm extends ConfigForm { + /** + * The backend to load when displaying the form for the first time + * + * @var string + */ + protected $backendToLoad; + /** * Initialize this form */ @@ -53,10 +60,7 @@ class UserGroupBackendForm extends ConfigForm throw new NotFoundError('No user group backend called "%s" found', $name); } - $data = $this->config->getSection($name)->toArray(); - $data['type'] = $data['backend']; - $data['name'] = $name; - $this->populate($data); + $this->backendToLoad = $name; return $this; } @@ -187,7 +191,8 @@ class UserGroupBackendForm extends ConfigForm 'autosubmit' => true, 'label' => $this->translate('Backend Type'), 'description' => $this->translate('The type of this user group backend'), - 'multiOptions' => $backendTypes + 'multiOptions' => $backendTypes, + 'value' => $backendType ) ); @@ -195,4 +200,17 @@ class UserGroupBackendForm extends ConfigForm $backendForm->createElements($formData); $this->addElements($backendForm->getElements()); } + + /** + * Populate the configuration of the backend to load + */ + public function onRequest() + { + if ($this->backendToLoad) { + $data = $this->config->getSection($this->backendToLoad)->toArray(); + $data['type'] = $data['backend']; + $data['name'] = $this->backendToLoad; + $this->populate($data); + } + } } From 447088af22319efc5d51fc5546644ce1488b8e90 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 5 Jun 2015 14:52:46 +0200 Subject: [PATCH 052/116] Introduce form LdapUserGroupBackendForm refs #7343 --- .../UserGroup/LdapUserGroupBackendForm.php | 304 ++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100644 application/forms/Config/UserGroup/LdapUserGroupBackendForm.php diff --git a/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php b/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php new file mode 100644 index 000000000..71af892f2 --- /dev/null +++ b/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php @@ -0,0 +1,304 @@ +setName('form_config_ldapusergroupbackend'); + } + + /** + * Create and add elements to this form + * + * @param array $formData + */ + public function createElements(array $formData) + { + $resourceNames = $this->getLdapResourceNames(); + $this->addElement( + 'select', + 'resource', + array( + 'required' => true, + 'autosubmit' => true, + 'label' => $this->translate('LDAP Connection'), + 'description' => $this->translate('The LDAP connection to use for this backend.'), + 'multiOptions' => array_combine($resourceNames, $resourceNames) + ) + ); + $resource = ResourceFactory::create( + isset($formData['resource']) && in_array($formData['resource'], $resourceNames) + ? $formData['resource'] + : $resourceNames[0] + ); + + $userBackends = array('none' => $this->translate('None', 'usergroupbackend.ldap.user_backend')); + $userBackendNames = $this->getLdapUserBackendNames($resource); + if (! empty($userBackendNames)) { + $userBackends = array_merge($userBackends, array_combine($userBackendNames, $userBackendNames)); + } + $this->addElement( + 'select', + 'user_backend', + array( + 'required' => true, + 'autosubmit' => true, + 'label' => $this->translate('User Backend'), + 'description' => $this->translate('The user backend to link with this user group backend.'), + 'multiOptions' => $userBackends + ) + ); + + $groupBackend = new LdapUserGroupBackend($resource); + if ($formData['type'] === 'ldap') { + $defaults = $groupBackend->getOpenLdapDefaults(); + $disabled = null; // MUST BE null, do NOT change this to false! + } else { // $formData['type'] === 'msldap' + $defaults = $groupBackend->getActiveDirectoryDefaults(); + $disabled = true; + } + + if (isset($formData['user_backend']) && $formData['user_backend'] !== 'none') { + $userBackend = UserBackend::create($formData['user_backend']); + $defaults->merge(array( + 'user_base_dn' => $userBackend->getBaseDn(), + 'user_class' => $userBackend->getUserClass(), + 'user_name_attribute' => $userBackend->getUserNameAttribute(), + 'user_filter' => $userBackend->getFilter() + )); + $disabled = true; + } + + $this->createGroupConfigElements($defaults, $disabled); + $this->createUserConfigElements($defaults, $disabled); + } + + /** + * Create and add all elements to this form required for the group configuration + * + * @param ConfigObject $defaults + * @param null|bool $disabled + */ + protected function createGroupConfigElements(ConfigObject $defaults, $disabled) + { + $this->addElement( + 'text', + 'group_class', + array( + 'preserveDefault' => true, + 'disabled' => $disabled, + 'label' => $this->translate('LDAP Group Object Class'), + 'description' => $this->translate('The object class used for storing groups on the LDAP server.'), + 'value' => $defaults->group_class + ) + ); + $this->addElement( + 'text', + 'group_filter', + array( + 'preserveDefault' => true, + 'allowEmpty' => true, + 'disabled' => $disabled, + 'label' => $this->translate('LDAP Group Filter'), + 'description' => $this->translate( + 'An additional filter to use when looking up groups using the specified connection. ' + . 'Leave empty to not to use any additional filter rules.' + ), + 'requirement' => $this->translate( + 'The filter needs to be expressed as standard LDAP expression, without' + . ' outer parentheses. (e.g. &(foo=bar)(bar=foo) or foo=bar)' + ), + 'validators' => array( + array( + 'Callback', + false, + array( + 'callback' => function ($v) { + return strpos($v, '(') !== 0; + }, + 'messages' => array( + 'callbackValue' => $this->translate('The filter must not be wrapped in parantheses.') + ) + ) + ) + ), + 'value' => $defaults->group_filter + ) + ); + $this->addElement( + 'text', + 'group_name_attribute', + array( + 'preserveDefault' => true, + 'disabled' => $disabled, + 'label' => $this->translate('LDAP Group Name Attribute'), + 'description' => $this->translate( + 'The attribute name used for storing a group\'s name on the LDAP server.' + ), + 'value' => $defaults->group_name_attribute + ) + ); + $this->addElement( + 'text', + 'base_dn', + array( + 'preserveDefault' => true, + 'disabled' => $disabled, + 'label' => $this->translate('LDAP Group Base DN'), + 'description' => $this->translate( + 'The path where groups can be found on the LDAP server. Leave ' . + 'empty to select all users available using the specified connection.' + ), + 'value' => $defaults->base_dn + ) + ); + } + + /** + * Create and add all elements to this form required for the user configuration + * + * @param ConfigObject $defaults + * @param null|bool $disabled + */ + protected function createUserConfigElements(ConfigObject $defaults, $disabled) + { + $this->addElement( + 'text', + 'user_class', + array( + 'preserveDefault' => true, + 'disabled' => $disabled, + 'label' => $this->translate('LDAP User Object Class'), + 'description' => $this->translate('The object class used for storing users on the LDAP server.'), + 'value' => $defaults->user_class + ) + ); + $this->addElement( + 'text', + 'user_filter', + array( + 'preserveDefault' => true, + 'allowEmpty' => true, + 'disabled' => $disabled, + 'label' => $this->translate('LDAP User Filter'), + 'description' => $this->translate( + 'An additional filter to use when looking up users using the specified connection. ' + . 'Leave empty to not to use any additional filter rules.' + ), + 'requirement' => $this->translate( + 'The filter needs to be expressed as standard LDAP expression, without' + . ' outer parentheses. (e.g. &(foo=bar)(bar=foo) or foo=bar)' + ), + 'validators' => array( + array( + 'Callback', + false, + array( + 'callback' => function ($v) { + return strpos($v, '(') !== 0; + }, + 'messages' => array( + 'callbackValue' => $this->translate('The filter must not be wrapped in parantheses.') + ) + ) + ) + ), + 'value' => $defaults->user_filter + ) + ); + $this->addElement( + 'text', + 'user_name_attribute', + array( + 'preserveDefault' => true, + 'disabled' => $disabled, + 'label' => $this->translate('LDAP User Name Attribute'), + 'description' => $this->translate( + 'The attribute name used for storing a user\'s name on the LDAP server.' + ), + 'value' => $defaults->user_name_attribute + ) + ); + $this->addElement( + 'text', + 'user_base_dn', + array( + 'preserveDefault' => true, + 'disabled' => $disabled, + 'label' => $this->translate('LDAP User Base DN'), + 'description' => $this->translate( + 'The path where users can be found on the LDAP server. Leave ' . + 'empty to select all users available using the specified connection.' + ), + 'value' => $defaults->user_base_dn + ) + ); + } + + /** + * Return the names of all configured LDAP resources + * + * @return array + */ + protected function getLdapResourceNames() + { + $names = array(); + foreach (ResourceFactory::getResourceConfigs() as $name => $config) { + if (in_array(strtolower($config->type), array('ldap', 'msldap'))) { + $names[] = $name; + } + } + + if (empty($names)) { + Notification::error( + $this->translate('No LDAP resources available. Please configure an LDAP resource first.') + ); + $this->getResponse()->redirectAndExit('config/createresource'); + } + + return $names; + } + + /** + * Return the names of all configured LDAP user backends + * + * @param Connection $resource + * + * @return array + */ + protected function getLdapUserBackendNames(Connection $resource) + { + $names = array(); + foreach (Config::app('authentication') as $name => $config) { + if (in_array(strtolower($config->backend), array('ldap', 'msldap'))) { + $backendResource = ResourceFactory::create($config->resource); + if ( + $backendResource->getHostname() === $resource->getHostname() + && $backendResource->getPort() === $resource->getPort() + ) { + $names[] = $name; + } + } + } + + return $names; + } +} From 5688f0cb857ce216dc831c747978c606ac1a2696 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 5 Jun 2015 14:53:29 +0200 Subject: [PATCH 053/116] Allow to configure user group backends of type LDAP refs #7343 --- .../Config/UserGroup/UserGroupBackendForm.php | 41 ++++++++++++++----- .../UserGroup/LdapUserGroupBackend.php | 2 +- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/application/forms/Config/UserGroup/UserGroupBackendForm.php b/application/forms/Config/UserGroup/UserGroupBackendForm.php index abcf1eaa4..68b3d2816 100644 --- a/application/forms/Config/UserGroup/UserGroupBackendForm.php +++ b/application/forms/Config/UserGroup/UserGroupBackendForm.php @@ -38,10 +38,17 @@ class UserGroupBackendForm extends ConfigForm */ public function getBackendForm($type) { - if ($type === 'db') { - return new DbUserGroupBackendForm(); - } else { - throw new InvalidArgumentException(sprintf($this->translate('Invalid backend type "%s" provided'), $type)); + switch ($type) + { + case 'db': + return new DbUserGroupBackendForm(); + case 'ldap': + case 'msldap': + return new LdapUserGroupBackendForm(); + default: + throw new InvalidArgumentException( + sprintf($this->translate('Invalid backend type "%s" provided'), $type) + ); } } @@ -165,7 +172,9 @@ class UserGroupBackendForm extends ConfigForm // TODO(jom): We did not think about how to configure custom group backends yet! $backendTypes = array( - 'db' => $this->translate('Database') + 'db' => $this->translate('Database'), + 'ldap' => $this->translate('LDAP'), + 'msldap' => $this->translate('ActiveDirectory') ); $backendType = isset($formData['type']) ? $formData['type'] : null; @@ -191,14 +200,11 @@ class UserGroupBackendForm extends ConfigForm 'autosubmit' => true, 'label' => $this->translate('Backend Type'), 'description' => $this->translate('The type of this user group backend'), - 'multiOptions' => $backendTypes, - 'value' => $backendType + 'multiOptions' => $backendTypes ) ); - $backendForm = $this->getBackendForm($backendType); - $backendForm->createElements($formData); - $this->addElements($backendForm->getElements()); + $this->addSubForm($this->getBackendForm($backendType)->create($formData), 'backend_form'); } /** @@ -213,4 +219,19 @@ class UserGroupBackendForm extends ConfigForm $this->populate($data); } } + + /** + * 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/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index 60b94ec27..224013e9f 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -556,7 +556,7 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken $defaults = new ConfigObject(); } - if ($config->user_backend) { + if ($config->user_backend && $config->user_backend !== 'none') { $userBackend = UserBackend::create($config->user_backend); if (! $userBackend instanceof LdapUserBackend) { throw new ConfigurationError('User backend "%s" is not of type LDAP', $config->user_backend); From 6dedb48444701d65251a1b4427374edf8ef0a795 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 5 Jun 2015 15:37:31 +0200 Subject: [PATCH 054/116] Indicate that nothing was found in the docs when searching refs #8847 --- modules/doc/application/views/scripts/search/index.phtml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/doc/application/views/scripts/search/index.phtml b/modules/doc/application/views/scripts/search/index.phtml index 3311c47a1..3d220392e 100644 --- a/modules/doc/application/views/scripts/search/index.phtml +++ b/modules/doc/application/views/scripts/search/index.phtml @@ -1,8 +1,8 @@
    $search): ?> - isEmpty()): ?> -

    escape($title) ?>

    - - +

    escape($title) ?>

    + isEmpty() + ? $this->translate('No documentation found matching the filter') + : $search ?>
    From e9e97cb1b377ff94fae2bb4281354c88bcb4e7d9 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 5 Jun 2015 15:41:24 +0200 Subject: [PATCH 055/116] UserGroupBackendForm: Unset the `name' property, always refs #7343 --- .../forms/Config/UserGroup/UserGroupBackendForm.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/application/forms/Config/UserGroup/UserGroupBackendForm.php b/application/forms/Config/UserGroup/UserGroupBackendForm.php index 68b3d2816..e8439ccaf 100644 --- a/application/forms/Config/UserGroup/UserGroupBackendForm.php +++ b/application/forms/Config/UserGroup/UserGroupBackendForm.php @@ -114,9 +114,12 @@ class UserGroupBackendForm extends ConfigForm } $backendConfig = $this->config->getSection($name); - if (isset($data['name']) && $data['name'] !== $name) { - $this->config->removeSection($name); - $name = $data['name']; + if (isset($data['name'])) { + if ($data['name'] !== $name) { + $this->config->removeSection($name); + $name = $data['name']; + } + unset($data['name']); } From e6837cf9e9612c3d837090eabb6be9505f851392 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 5 Jun 2015 15:42:35 +0200 Subject: [PATCH 056/116] UsergroupbackendController: Do not persist empty configuration values refs #7343 --- application/controllers/UsergroupbackendController.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/application/controllers/UsergroupbackendController.php b/application/controllers/UsergroupbackendController.php index cdb6826be..1a3c2f46e 100644 --- a/application/controllers/UsergroupbackendController.php +++ b/application/controllers/UsergroupbackendController.php @@ -52,7 +52,7 @@ class UsergroupbackendController extends Controller $form->setIniConfig(Config::app('groups')); $form->setOnSuccess(function (UserGroupBackendForm $form) { try { - $form->add($form->getValues()); + $form->add(array_filter($form->getValues())); } catch (Exception $e) { $form->error($e->getMessage()); return false; @@ -85,7 +85,12 @@ class UsergroupbackendController extends Controller $form->setIniConfig(Config::app('groups')); $form->setOnSuccess(function (UserGroupBackendForm $form) use ($backendName) { try { - $form->edit($backendName, $form->getValues()); + $form->edit($backendName, array_map( + function ($v) { + return $v !== '' ? $v : null; + }, + $form->getValues() + )); } catch (Exception $e) { $form->error($e->getMessage()); return false; From 975edbe548ecacabee22fb871e21dd2b8312dc19 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 5 Jun 2015 15:45:33 +0200 Subject: [PATCH 057/116] UserGroupBackendForm: Do not persist null values, really Revert this once #9376 has been fixed. refs #9376 --- .../forms/Config/UserGroup/UserGroupBackendForm.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/application/forms/Config/UserGroup/UserGroupBackendForm.php b/application/forms/Config/UserGroup/UserGroupBackendForm.php index e8439ccaf..cfd615e47 100644 --- a/application/forms/Config/UserGroup/UserGroupBackendForm.php +++ b/application/forms/Config/UserGroup/UserGroupBackendForm.php @@ -123,7 +123,14 @@ class UserGroupBackendForm extends ConfigForm unset($data['name']); } - $this->config->setSection($name, $backendConfig->merge($data)); + $backendConfig->merge($data); + foreach ($backendConfig->toArray() as $k => $v) { + if ($v === null) { + unset($backendConfig->$k); + } + } + + $this->config->setSection($name, $backendConfig); return $this; } From 05b7c3aa6a57b355687ed94784e2a6d5e1e14d8a Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 5 Jun 2015 16:01:37 +0200 Subject: [PATCH 058/116] Replace

    w/

    refs #8847 --- modules/doc/application/views/scripts/search/index.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/doc/application/views/scripts/search/index.phtml b/modules/doc/application/views/scripts/search/index.phtml index 3d220392e..c613f04df 100644 --- a/modules/doc/application/views/scripts/search/index.phtml +++ b/modules/doc/application/views/scripts/search/index.phtml @@ -1,6 +1,6 @@
    $search): ?> -

    escape($title) ?>

    +

    escape($title) ?>

    isEmpty() ? $this->translate('No documentation found matching the filter') : $search ?> From 24cb6bfc6e8ec3f9712e07524224670d8faebd64 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 5 Jun 2015 16:01:52 +0200 Subject: [PATCH 059/116] Form: Preserve defaults for subforms as well --- library/Icinga/Web/Form.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/library/Icinga/Web/Form.php b/library/Icinga/Web/Form.php index 48f7acdb7..08cb02388 100644 --- a/library/Icinga/Web/Form.php +++ b/library/Icinga/Web/Form.php @@ -855,8 +855,19 @@ class Form extends Zend_Form public function populate(array $defaults) { $this->create($defaults); + $this->preserveDefaults($this, $defaults); + return parent::populate($defaults); + } - foreach ($this->getElements() as $name => $_) { + /** + * Recurse the given form and unset all unchanged default values + * + * @param Zend_Form $form + * @param array $defaults + */ + protected function preserveDefaults(Zend_Form $form, array & $defaults) + { + foreach ($form->getElements() as $name => $_) { if ( array_key_exists($name, $defaults) && array_key_exists($name . static::DEFAULT_SUFFIX, $defaults) @@ -866,7 +877,9 @@ class Form extends Zend_Form } } - return parent::populate($defaults); + foreach ($form->getSubForms() as $_ => $subForm) { + $this->preserveDefaults($subForm, $defaults); + } } /** From 797e9de6297007b8af806c41d47be2bf443db43f Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 5 Jun 2015 16:07:27 +0200 Subject: [PATCH 060/116] LdapUserGroupBackendForm: Do not disable group configuration.. ..when no user backend has been linked and it's not a ActiveDirectory. refs #7343 --- .../Config/UserGroup/LdapUserGroupBackendForm.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php b/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php index 71af892f2..3e97f4dd4 100644 --- a/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php +++ b/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php @@ -70,10 +70,10 @@ class LdapUserGroupBackendForm extends Form $groupBackend = new LdapUserGroupBackend($resource); if ($formData['type'] === 'ldap') { $defaults = $groupBackend->getOpenLdapDefaults(); - $disabled = null; // MUST BE null, do NOT change this to false! + $groupConfigDisabled = $userConfigDisabled = null; // MUST BE null, do NOT change this to false! } else { // $formData['type'] === 'msldap' $defaults = $groupBackend->getActiveDirectoryDefaults(); - $disabled = true; + $groupConfigDisabled = $userConfigDisabled = true; } if (isset($formData['user_backend']) && $formData['user_backend'] !== 'none') { @@ -84,11 +84,11 @@ class LdapUserGroupBackendForm extends Form 'user_name_attribute' => $userBackend->getUserNameAttribute(), 'user_filter' => $userBackend->getFilter() )); - $disabled = true; + $userConfigDisabled = true; } - $this->createGroupConfigElements($defaults, $disabled); - $this->createUserConfigElements($defaults, $disabled); + $this->createGroupConfigElements($defaults, $groupConfigDisabled); + $this->createUserConfigElements($defaults, $userConfigDisabled); } /** From e910a5a254ba834d7db01925d0e259d21b57ced3 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 5 Jun 2015 16:16:35 +0200 Subject: [PATCH 061/116] LdapUserGroupBackendForm: Do not disable `base_dn' and `user_base_dn' refs #7343 --- .../Config/UserGroup/LdapUserGroupBackendForm.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php b/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php index 3e97f4dd4..5b6d42b89 100644 --- a/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php +++ b/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php @@ -76,6 +76,7 @@ class LdapUserGroupBackendForm extends Form $groupConfigDisabled = $userConfigDisabled = true; } + $dnDisabled = null; // MUST BE null if (isset($formData['user_backend']) && $formData['user_backend'] !== 'none') { $userBackend = UserBackend::create($formData['user_backend']); $defaults->merge(array( @@ -84,11 +85,11 @@ class LdapUserGroupBackendForm extends Form 'user_name_attribute' => $userBackend->getUserNameAttribute(), 'user_filter' => $userBackend->getFilter() )); - $userConfigDisabled = true; + $userConfigDisabled = $dnDisabled = true; } $this->createGroupConfigElements($defaults, $groupConfigDisabled); - $this->createUserConfigElements($defaults, $userConfigDisabled); + $this->createUserConfigElements($defaults, $userConfigDisabled, $dnDisabled); } /** @@ -161,7 +162,6 @@ class LdapUserGroupBackendForm extends Form 'base_dn', array( 'preserveDefault' => true, - 'disabled' => $disabled, 'label' => $this->translate('LDAP Group Base DN'), 'description' => $this->translate( 'The path where groups can be found on the LDAP server. Leave ' . @@ -177,8 +177,9 @@ class LdapUserGroupBackendForm extends Form * * @param ConfigObject $defaults * @param null|bool $disabled + * @param null|bool $dnDisabled */ - protected function createUserConfigElements(ConfigObject $defaults, $disabled) + protected function createUserConfigElements(ConfigObject $defaults, $disabled, $dnDisabled) { $this->addElement( 'text', @@ -242,7 +243,7 @@ class LdapUserGroupBackendForm extends Form 'user_base_dn', array( 'preserveDefault' => true, - 'disabled' => $disabled, + 'disabled' => $dnDisabled, 'label' => $this->translate('LDAP User Base DN'), 'description' => $this->translate( 'The path where users can be found on the LDAP server. Leave ' . From 840069b436437920e66db2b93134921d76eb9769 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 5 Jun 2015 16:34:28 +0200 Subject: [PATCH 062/116] LdapUserGroupBackendForm: Do not persist default values ... refs #7343 --- .../forms/Config/UserGroup/LdapUserGroupBackendForm.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php b/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php index 5b6d42b89..1ce6af713 100644 --- a/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php +++ b/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php @@ -105,6 +105,7 @@ class LdapUserGroupBackendForm extends Form 'group_class', array( 'preserveDefault' => true, + 'ignore' => $disabled, 'disabled' => $disabled, 'label' => $this->translate('LDAP Group Object Class'), 'description' => $this->translate('The object class used for storing groups on the LDAP server.'), @@ -117,6 +118,7 @@ class LdapUserGroupBackendForm extends Form array( 'preserveDefault' => true, 'allowEmpty' => true, + 'ignore' => $disabled, 'disabled' => $disabled, 'label' => $this->translate('LDAP Group Filter'), 'description' => $this->translate( @@ -149,6 +151,7 @@ class LdapUserGroupBackendForm extends Form 'group_name_attribute', array( 'preserveDefault' => true, + 'ignore' => $disabled, 'disabled' => $disabled, 'label' => $this->translate('LDAP Group Name Attribute'), 'description' => $this->translate( @@ -186,6 +189,7 @@ class LdapUserGroupBackendForm extends Form 'user_class', array( 'preserveDefault' => true, + 'ignore' => $disabled, 'disabled' => $disabled, 'label' => $this->translate('LDAP User Object Class'), 'description' => $this->translate('The object class used for storing users on the LDAP server.'), @@ -198,6 +202,7 @@ class LdapUserGroupBackendForm extends Form array( 'preserveDefault' => true, 'allowEmpty' => true, + 'ignore' => $disabled, 'disabled' => $disabled, 'label' => $this->translate('LDAP User Filter'), 'description' => $this->translate( @@ -230,6 +235,7 @@ class LdapUserGroupBackendForm extends Form 'user_name_attribute', array( 'preserveDefault' => true, + 'ignore' => $disabled, 'disabled' => $disabled, 'label' => $this->translate('LDAP User Name Attribute'), 'description' => $this->translate( @@ -243,6 +249,7 @@ class LdapUserGroupBackendForm extends Form 'user_base_dn', array( 'preserveDefault' => true, + 'ignore' => $dnDisabled, 'disabled' => $dnDisabled, 'label' => $this->translate('LDAP User Base DN'), 'description' => $this->translate( From c800f1e6aa6ce4f7daaa5c244c49ac52152b4153 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 5 Jun 2015 16:40:17 +0200 Subject: [PATCH 063/116] UserGroupBackendForm: Do not translate LDAP and ActiveDirectory refs #7343 --- application/forms/Config/UserGroup/UserGroupBackendForm.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/forms/Config/UserGroup/UserGroupBackendForm.php b/application/forms/Config/UserGroup/UserGroupBackendForm.php index cfd615e47..5bd0d9681 100644 --- a/application/forms/Config/UserGroup/UserGroupBackendForm.php +++ b/application/forms/Config/UserGroup/UserGroupBackendForm.php @@ -183,8 +183,8 @@ class UserGroupBackendForm extends ConfigForm // TODO(jom): We did not think about how to configure custom group backends yet! $backendTypes = array( 'db' => $this->translate('Database'), - 'ldap' => $this->translate('LDAP'), - 'msldap' => $this->translate('ActiveDirectory') + 'ldap' => 'LDAP', + 'msldap' => 'ActiveDirectory' ); $backendType = isset($formData['type']) ? $formData['type'] : null; From 76e748c483fb79efc134ef6b492b60131fec56c4 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 5 Jun 2015 17:12:32 +0200 Subject: [PATCH 064/116] Don't throw any exception refs #8705 --- library/Icinga/Application/Platform.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/library/Icinga/Application/Platform.php b/library/Icinga/Application/Platform.php index a6e0d9604..cc72857f9 100644 --- a/library/Icinga/Application/Platform.php +++ b/library/Icinga/Application/Platform.php @@ -3,8 +3,6 @@ namespace Icinga\Application; -use Icinga\Exception\IcingaException; - /** * Platform tests for icingaweb */ @@ -95,7 +93,7 @@ class Platform foreach ($osRelease as $osInfo) { if (false === ($res = @preg_match('/(? Date: Fri, 5 Jun 2015 17:20:31 +0200 Subject: [PATCH 065/116] UserBackendConfigForm: Allow to configure user backends of type msldap fixes #9355 --- .../Config/UserBackend/LdapBackendForm.php | 33 ++++++++++++------- .../forms/Config/UserBackendConfigForm.php | 29 ++++++++++------ .../UserBackend/LdapBackendFormTest.php | 23 ++++++------- 3 files changed, 50 insertions(+), 35 deletions(-) diff --git a/application/forms/Config/UserBackend/LdapBackendForm.php b/application/forms/Config/UserBackend/LdapBackendForm.php index c520ec68f..ac2091398 100644 --- a/application/forms/Config/UserBackend/LdapBackendForm.php +++ b/application/forms/Config/UserBackend/LdapBackendForm.php @@ -8,7 +8,7 @@ use Icinga\Web\Form; use Icinga\Data\ConfigObject; use Icinga\Data\ResourceFactory; use Icinga\Exception\AuthenticationException; -use Icinga\Authentication\User\LdapUserBackend; +use Icinga\Authentication\User\UserBackend; /** * Form class for adding/modifying LDAP user backends @@ -48,6 +48,8 @@ class LdapBackendForm extends Form */ public function createElements(array $formData) { + $isAd = isset($formData['type']) ? $formData['type'] === 'msldap' : false; + $this->addElement( 'text', 'name', @@ -77,10 +79,13 @@ class LdapBackendForm extends Form 'text', 'user_class', array( - 'required' => true, - 'label' => $this->translate('LDAP User Object Class'), - 'description' => $this->translate('The object class used for storing users on the LDAP server.'), - 'value' => 'inetOrgPerson' + 'preserveDefault' => true, + 'required' => ! $isAd, + 'ignore' => $isAd, + 'disabled' => $isAd ?: null, + 'label' => $this->translate('LDAP User Object Class'), + 'description' => $this->translate('The object class used for storing users on the LDAP server.'), + 'value' => $isAd ? 'user' : 'inetOrgPerson' ) ); $this->addElement( @@ -117,12 +122,15 @@ class LdapBackendForm extends Form 'text', 'user_name_attribute', array( - 'required' => true, - 'label' => $this->translate('LDAP User Name Attribute'), - 'description' => $this->translate( + 'preserveDefault' => true, + 'required' => ! $isAd, + 'ignore' => $isAd, + 'disabled' => $isAd ?: null, + 'label' => $this->translate('LDAP User Name Attribute'), + 'description' => $this->translate( 'The attribute name used for storing the user name on the LDAP server.' ), - 'value' => 'uid' + 'value' => $isAd ? 'sAMAccountName' : 'uid' ) ); $this->addElement( @@ -130,7 +138,7 @@ class LdapBackendForm extends Form 'backend', array( 'disabled' => true, - 'value' => 'ldap' + 'value' => $isAd ? 'msldap' : 'ldap' ) ); $this->addElement( @@ -170,8 +178,7 @@ class LdapBackendForm extends Form public static function isValidUserBackend(Form $form) { try { - $ldapUserBackend = new LdapUserBackend(ResourceFactory::createResource($form->getResourceConfig())); - $ldapUserBackend->setConfig(new ConfigObject($form->getValues())); + $ldapUserBackend = UserBackend::create(null, new ConfigObject($form->getValues())); $ldapUserBackend->assertAuthenticationPossible(); } catch (AuthenticationException $e) { if (($previous = $e->getPrevious()) !== null) { @@ -193,6 +200,8 @@ class LdapBackendForm extends Form * Return the configuration for the chosen resource * * @return ConfigObject + * + * @todo Check whether it's possible to drop this (Or even all occurences!) */ public function getResourceConfig() { diff --git a/application/forms/Config/UserBackendConfigForm.php b/application/forms/Config/UserBackendConfigForm.php index 0a30dd590..62a68e70b 100644 --- a/application/forms/Config/UserBackendConfigForm.php +++ b/application/forms/Config/UserBackendConfigForm.php @@ -60,16 +60,24 @@ class UserBackendConfigForm extends ConfigForm */ public function getBackendForm($type) { - if ($type === 'db') { - $form = new DbBackendForm(); - $form->setResources(isset($this->resources['db']) ? $this->resources['db'] : array()); - } elseif ($type === 'ldap') { - $form = new LdapBackendForm(); - $form->setResources(isset($this->resources['ldap']) ? $this->resources['ldap'] : array()); - } elseif ($type === 'external') { - $form = new ExternalBackendForm(); - } else { - throw new InvalidArgumentException(sprintf($this->translate('Invalid backend type "%s" provided'), $type)); + switch ($type) + { + case 'db': + $form = new DbBackendForm(); + $form->setResources(isset($this->resources['db']) ? $this->resources['db'] : array()); + break; + case 'ldap': + case 'msldap': + $form = new LdapBackendForm(); + $form->setResources(isset($this->resources['ldap']) ? $this->resources['ldap'] : array()); + break; + case 'external': + $form = new ExternalBackendForm(); + break; + default: + throw new InvalidArgumentException( + sprintf($this->translate('Invalid backend type "%s" provided'), $type) + ); } return $form; @@ -296,6 +304,7 @@ class UserBackendConfigForm extends ConfigForm } if (isset($this->resources['ldap']) && ($backendType === 'ldap' || Platform::extensionLoaded('ldap'))) { $backendTypes['ldap'] = 'LDAP'; + $backendTypes['msldap'] = 'ActiveDirectory'; } $externalBackends = array_filter( diff --git a/test/php/application/forms/Config/UserBackend/LdapBackendFormTest.php b/test/php/application/forms/Config/UserBackend/LdapBackendFormTest.php index f7373a7ae..6fe63adf4 100644 --- a/test/php/application/forms/Config/UserBackend/LdapBackendFormTest.php +++ b/test/php/application/forms/Config/UserBackend/LdapBackendFormTest.php @@ -27,10 +27,9 @@ class LdapBackendFormTest extends BaseTestCase */ public function testValidBackendIsValid() { - $this->setUpResourceFactoryMock(); - Mockery::mock('overload:Icinga\Authentication\User\LdapUserBackend') - ->shouldReceive('assertAuthenticationPossible')->andReturnNull() - ->shouldReceive('setConfig')->andReturnNull(); + $ldapUserBackendMock = Mockery::mock('overload:Icinga\Authentication\User\LdapUserBackend'); + $ldapUserBackendMock->shouldReceive('assertAuthenticationPossible')->andReturnNull(); + $this->setUpUserBackendMock($ldapUserBackendMock); // Passing array(null) is required to make Mockery call the constructor... $form = Mockery::mock('Icinga\Forms\Config\UserBackend\LdapBackendForm[getView]', array(null)); @@ -53,9 +52,9 @@ class LdapBackendFormTest extends BaseTestCase */ public function testInvalidBackendIsNotValid() { - $this->setUpResourceFactoryMock(); - Mockery::mock('overload:Icinga\Authentication\User\LdapUserBackend') - ->shouldReceive('assertAuthenticationPossible')->andThrow(new AuthenticationException); + $ldapUserBackendMock = Mockery::mock('overload:Icinga\Authentication\User\LdapUserBackend'); + $ldapUserBackendMock->shouldReceive('assertAuthenticationPossible')->andThrow(new AuthenticationException); + $this->setUpUserBackendMock($ldapUserBackendMock); // Passing array(null) is required to make Mockery call the constructor... $form = Mockery::mock('Icinga\Forms\Config\UserBackend\LdapBackendForm[getView]', array(null)); @@ -72,12 +71,10 @@ class LdapBackendFormTest extends BaseTestCase ); } - protected function setUpResourceFactoryMock() + protected function setUpUserBackendMock($ldapUserBackendMock) { - Mockery::mock('alias:Icinga\Data\ResourceFactory') - ->shouldReceive('createResource') - ->andReturn(Mockery::mock('Icinga\Protocol\Ldap\Connection')) - ->shouldReceive('getResourceConfig') - ->andReturn(new ConfigObject()); + Mockery::mock('alias:Icinga\Authentication\User\UserBackend') + ->shouldReceive('create') + ->andReturn($ldapUserBackendMock); } } From 2dd04bb6c96cabce1a8a9ed7e72f627ef6b582bd Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 5 Jun 2015 17:24:03 +0200 Subject: [PATCH 066/116] LdapUserGroupBackendForm: Do not disable `user_filter' and `group_filter' refs #7343 --- .../forms/Config/UserGroup/LdapUserGroupBackendForm.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php b/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php index 1ce6af713..c3c8baacf 100644 --- a/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php +++ b/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php @@ -118,8 +118,6 @@ class LdapUserGroupBackendForm extends Form array( 'preserveDefault' => true, 'allowEmpty' => true, - 'ignore' => $disabled, - 'disabled' => $disabled, 'label' => $this->translate('LDAP Group Filter'), 'description' => $this->translate( 'An additional filter to use when looking up groups using the specified connection. ' @@ -202,8 +200,8 @@ class LdapUserGroupBackendForm extends Form array( 'preserveDefault' => true, 'allowEmpty' => true, - 'ignore' => $disabled, - 'disabled' => $disabled, + 'ignore' => $dnDisabled, + 'disabled' => $dnDisabled, 'label' => $this->translate('LDAP User Filter'), 'description' => $this->translate( 'An additional filter to use when looking up users using the specified connection. ' From e15b144155dc9c989eefa57749409e2883f58b49 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 8 Jun 2015 11:16:59 +0200 Subject: [PATCH 067/116] Revert "Rename .fileNotReadable to .message-error" This reverts commit 6b14195809ce51bdf6f1c803af165438e948bd72. --- public/css/icinga/layout-structure.less | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/css/icinga/layout-structure.less b/public/css/icinga/layout-structure.less index 5b5eee5ef..3a8713a38 100644 --- a/public/css/icinga/layout-structure.less +++ b/public/css/icinga/layout-structure.less @@ -331,7 +331,8 @@ html { position: absolute; } -.message-error { +/* TODO: replace this with .error */ +.fileNotReadable { padding: 0.5em; background-color: @colorCritical; font-weight: bold; From 0b1bb54c26f5115367cf7e5655ec63bd6d74b1d5 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 5 Jun 2015 12:38:55 +0200 Subject: [PATCH 068/116] Rename .fileNotReadable to .message-error refs #9247 --- application/views/scripts/about/index.phtml | 2 +- public/css/icinga/layout-structure.less | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/application/views/scripts/about/index.phtml b/application/views/scripts/about/index.phtml index 6a01e1bf9..e270a8eb9 100644 --- a/application/views/scripts/about/index.phtml +++ b/application/views/scripts/about/index.phtml @@ -16,7 +16,7 @@ echo ( 0 === count($versionInfo) - ? '

    ' . $this->translate( + ? '

    ' . $this->translate( 'Can\'t determine Icinga Web 2\'s version' ) : '

    ' . nl2br(implode("\n", $versionInfo), false) diff --git a/public/css/icinga/layout-structure.less b/public/css/icinga/layout-structure.less index 3a8713a38..5b5eee5ef 100644 --- a/public/css/icinga/layout-structure.less +++ b/public/css/icinga/layout-structure.less @@ -331,8 +331,7 @@ html { position: absolute; } -/* TODO: replace this with .error */ -.fileNotReadable { +.message-error { padding: 0.5em; background-color: @colorCritical; font-weight: bold; From 54b960addca31bba33684b552a575047534a63da Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 8 Jun 2015 12:39:31 +0200 Subject: [PATCH 069/116] RoleController: Fix link to add a new role --- application/views/scripts/role/list.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/views/scripts/role/list.phtml b/application/views/scripts/role/list.phtml index 766ba26f3..568bbdf98 100644 --- a/application/views/scripts/role/list.phtml +++ b/application/views/scripts/role/list.phtml @@ -67,7 +67,7 @@ - + translate('Create a New Role') ?>

    From ddba1d86fa0d82eae257067bfac78ccf371dd80b Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Mon, 8 Jun 2015 12:22:27 +0200 Subject: [PATCH 070/116] Fix spacing in ini files refs #8706 --- library/Icinga/File/Ini/IniEditor.php | 25 ++++++++++++------------- library/Icinga/File/Ini/IniWriter.php | 2 +- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/library/Icinga/File/Ini/IniEditor.php b/library/Icinga/File/Ini/IniEditor.php index 1f8202d9d..035e10037 100644 --- a/library/Icinga/File/Ini/IniEditor.php +++ b/library/Icinga/File/Ini/IniEditor.php @@ -313,14 +313,19 @@ class IniEditor */ public function getText() { - $this->cleanUpWhitespaces(); - return rtrim(implode(PHP_EOL, $this->text)) . PHP_EOL; + $this->normalizeSectionSpacing(); + + // trim leading and trailing whitespaces from generated file + $txt = trim(implode(PHP_EOL, $this->text)) . PHP_EOL; + + // replace linebreaks, unless they preceed a comment or a section + return preg_replace("/\n[\n]*([^;\[])/", "\n$1", $txt); } /** - * Remove all unneeded line breaks between sections + * normalize section spacing according to the current settings */ - private function cleanUpWhitespaces() + private function normalizeSectionSpacing() { $i = count($this->text) - 1; for (; $i > 0; $i--) { @@ -328,24 +333,18 @@ class IniEditor if ($this->isSectionDeclaration($line) && $i > 0) { $i--; $line = $this->text[$i]; - /* - * Ignore comments that are glued to the section declaration - */ + // ignore comments that are glued to the section declaration while ($i > 0 && $this->isComment($line)) { $i--; $line = $this->text[$i]; } - /* - * Remove whitespaces between the sections - */ + // remove whitespaces between the sections while ($i > 0 && preg_match('/^\s*$/', $line) === 1) { $this->deleteLine($i); $i--; $line = $this->text[$i]; } - /* - * Refresh section separators - */ + // refresh section separators if ($i !== 0 && $this->sectionSeparators > 0) { $this->insertAtLine($i + 1, str_repeat(PHP_EOL, $this->sectionSeparators - 1)); } diff --git a/library/Icinga/File/Ini/IniWriter.php b/library/Icinga/File/Ini/IniWriter.php index e43c226aa..5b565a6a0 100644 --- a/library/Icinga/File/Ini/IniWriter.php +++ b/library/Icinga/File/Ini/IniWriter.php @@ -60,7 +60,7 @@ class IniWriter extends Zend_Config_Writer_FileAbstract { if (file_exists($this->_filename)) { $oldconfig = new Zend_Config_Ini($this->_filename); - $content = file_get_contents($this->_filename); + $content = trim(file_get_contents($this->_filename)); } else { $oldconfig = new Zend_Config(array()); $content = ''; From 3600e1088c2c9d6d7e81a5aa3d4e114d0b0529cb Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Mon, 8 Jun 2015 13:10:21 +0200 Subject: [PATCH 071/116] Fix key and value sanitazion in ini writer --- library/Icinga/File/Ini/IniEditor.php | 2 +- .../library/Icinga/File/Ini/IniWriterTest.php | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/library/Icinga/File/Ini/IniEditor.php b/library/Icinga/File/Ini/IniEditor.php index 035e10037..00bea8df4 100644 --- a/library/Icinga/File/Ini/IniEditor.php +++ b/library/Icinga/File/Ini/IniEditor.php @@ -620,6 +620,6 @@ class IniEditor private function sanitize($value) { - return str_replace('\n', '', $value); + return str_replace("\n", '', $value); } } diff --git a/test/php/library/Icinga/File/Ini/IniWriterTest.php b/test/php/library/Icinga/File/Ini/IniWriterTest.php index 2d9659675..fa9ad895f 100644 --- a/test/php/library/Icinga/File/Ini/IniWriterTest.php +++ b/test/php/library/Icinga/File/Ini/IniWriterTest.php @@ -719,6 +719,34 @@ EOD; ); } + public function testWhetherLinebreaksAreRemoved() + { + $target = $this->writeConfigToTemporaryFile(''); + $writer = new IniWriter( + array( + 'config' => Config::fromArray( + array( + 'section' => array( + 'foo' => 'linebreak +in line', + 'linebreak +inkey' => 'blarg' + ) + ) + ), + 'filename' => $target + ) + ); + + $rendered = $writer->render(); + var_dump($rendered); + $this->assertEquals( + count(explode("\n", $rendered)), + 4, + 'generated config should not contain more than three line breaks' + ); + } + /** * Write a INI-configuration string to a temporary file and return its path * From 6e045b62393b1c132718874469ce4e63c513bcbb Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 8 Jun 2015 13:19:46 +0200 Subject: [PATCH 072/116] Change version in modules/*/module.info to 2.0.0-rc1 refs #7651 --- modules/doc/module.info | 2 +- modules/monitoring/module.info | 2 +- modules/test/module.info | 2 +- modules/translation/module.info | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/doc/module.info b/modules/doc/module.info index 2c6f48ac9..32516de5b 100644 --- a/modules/doc/module.info +++ b/modules/doc/module.info @@ -1,4 +1,4 @@ Module: doc -Version: 2.0.0 +Version: 2.0.0-rc1 Description: Documentation module Extracts, shows and exports documentation for Icinga Web 2 and its modules. diff --git a/modules/monitoring/module.info b/modules/monitoring/module.info index a807a5316..6e36c2838 100644 --- a/modules/monitoring/module.info +++ b/modules/monitoring/module.info @@ -1,5 +1,5 @@ Module: monitoring -Version: 2.0.0~alpha4 +Version: 2.0.0-rc1 Description: Icinga monitoring module This is the core module for most Icingaweb users. It provides an abstraction layer for various Icinga data backends. diff --git a/modules/test/module.info b/modules/test/module.info index ed6adc1f2..b29ad31eb 100644 --- a/modules/test/module.info +++ b/modules/test/module.info @@ -1,5 +1,5 @@ Module: test -Version: 2.0.0~alpha4 +Version: 2.0.0-rc1 Description: Translation module This module allows developers to run (unit) tests against Icinga Web 2 and any of its modules. Usually you do not need to enable this. diff --git a/modules/translation/module.info b/modules/translation/module.info index ebe121ebc..a8f4013c7 100644 --- a/modules/translation/module.info +++ b/modules/translation/module.info @@ -1,5 +1,5 @@ Module: translation -Version: 2.0.0~alpha4 +Version: 2.0.0-rc1 Description: Translation module This module allows developers and translators to translate Icinga Web 2 and its modules for multiple languages. You do not need this module to run an From e9d308eb620e1719087c1181b839330e3cc1e9c7 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 8 Jun 2015 13:26:30 +0200 Subject: [PATCH 073/116] Setup: add module.info refs #7651 --- modules/setup/module.info | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 modules/setup/module.info diff --git a/modules/setup/module.info b/modules/setup/module.info new file mode 100644 index 000000000..0faa4ed0f --- /dev/null +++ b/modules/setup/module.info @@ -0,0 +1,4 @@ +Module: setup +Version: 2.0.0-rc1 +Description: Setup module + Sets up Icinga Web 2. From 07041fafc8f617254dc7243da5002db1563d5698 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 8 Jun 2015 13:27:45 +0200 Subject: [PATCH 074/116] UserController: Behave nicely when not any backend is available --- application/controllers/UserController.php | 4 ++++ application/views/scripts/user/list.phtml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/application/controllers/UserController.php b/application/controllers/UserController.php index e944af52c..5abdaece8 100644 --- a/application/controllers/UserController.php +++ b/application/controllers/UserController.php @@ -28,6 +28,10 @@ class UserController extends AuthBackendController function ($b) { return $b->getName(); }, $this->loadUserBackends('Icinga\Data\Selectable') ); + if (empty($backendNames)) { + return; + } + $this->view->backendSelection = new Form(); $this->view->backendSelection->setAttrib('class', 'backend-selection'); $this->view->backendSelection->setUidDisabled(); diff --git a/application/views/scripts/user/list.phtml b/application/views/scripts/user/list.phtml index 76a6f2b8b..df9a14a45 100644 --- a/application/views/scripts/user/list.phtml +++ b/application/views/scripts/user/list.phtml @@ -18,7 +18,7 @@ if (! $this->compact): ?>
    translate('No backend found which is able to list users') . '
    '; return; } else { From 91cee3a957cf0d272aa9cfcaad788c36e9828b3a Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 8 Jun 2015 13:28:12 +0200 Subject: [PATCH 075/116] GroupController: Behave nicely when not any backend is available --- application/controllers/GroupController.php | 4 ++++ application/views/scripts/group/list.phtml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/application/controllers/GroupController.php b/application/controllers/GroupController.php index 8f2c33e62..395b2f243 100644 --- a/application/controllers/GroupController.php +++ b/application/controllers/GroupController.php @@ -28,6 +28,10 @@ class GroupController extends AuthBackendController function ($b) { return $b->getName(); }, $this->loadUserGroupBackends('Icinga\Data\Selectable') ); + if (empty($backendNames)) { + return; + } + $this->view->backendSelection = new Form(); $this->view->backendSelection->setAttrib('class', 'backend-selection'); $this->view->backendSelection->setUidDisabled(); diff --git a/application/views/scripts/group/list.phtml b/application/views/scripts/group/list.phtml index bcd9dca93..65c1591cd 100644 --- a/application/views/scripts/group/list.phtml +++ b/application/views/scripts/group/list.phtml @@ -18,7 +18,7 @@ if (! $this->compact): ?>
    translate('No backend found which is able to list groups') . '
    '; return; } else { From 44e3dba36226451afb2a3f0954ae21bc3bb6c7ba Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Mon, 8 Jun 2015 16:19:03 +0200 Subject: [PATCH 076/116] Improve group and user view layout Display header and hide filter and sort widgets when there are no entries to be sorted or filtered. --- application/views/scripts/group/show.phtml | 11 ++++++----- application/views/scripts/user/show.phtml | 11 ++++++----- public/css/icinga/main-content.less | 10 ++++++---- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/application/views/scripts/group/show.phtml b/application/views/scripts/group/show.phtml index 636a449d4..8c8c91f51 100644 --- a/application/views/scripts/group/show.phtml +++ b/application/views/scripts/group/show.phtml @@ -30,8 +30,12 @@ if ($this->hasPermission('config/authentication/groups/edit') && $backend instan

    escape($group->group_name); ?>

    translate('Created at'); ?>: created_at === null ? '-' : $this->formatDateTime($group->created_at); ?>

    -

    translate('Last modified'); ?>: last_modified === null ? '-' : $this->formatDateTime($group->last_modified); ?>

    +

    translate('Last modified'); ?>: last_modified === null ? '-' : $this->formatDateTime($group->last_modified); ?>

    +
    translate('Members'); ?>
    +

    +
    + 0): ?> compact): ?> sortBox; ?> @@ -40,9 +44,6 @@ if ($this->hasPermission('config/authentication/groups/edit') && $backend instan compact): ?> filterEditor; ?> -
    -
    - 0): ?> @@ -66,7 +67,7 @@ if ($this->hasPermission('config/authentication/groups/edit') && $backend instan
    -

    translate('No group member found matching the filter'); ?>

    +

    translate('Group has no members.'); ?>

    qlink($this->translate('Add a new member'), 'group/addmember', array( diff --git a/application/views/scripts/user/show.phtml b/application/views/scripts/user/show.phtml index 82f4c53f9..f4e9dee6a 100644 --- a/application/views/scripts/user/show.phtml +++ b/application/views/scripts/user/show.phtml @@ -30,8 +30,12 @@ if ($this->hasPermission('config/authentication/users/edit') && $backend instanc

    escape($user->user_name); ?>

    translate('State'); ?>: is_active === null ? '-' : ($user->is_active ? $this->translate('Active') : $this->translate('Inactive')); ?>

    translate('Created at'); ?>: created_at === null ? '-' : $this->formatDateTime($user->created_at); ?>

    -

    translate('Last modified'); ?>: last_modified === null ? '-' : $this->formatDateTime($user->last_modified); ?>

    +

    translate('Last modified'); ?>: last_modified === null ? '-' : $this->formatDateTime($user->last_modified); ?>

    +
    translate('Group Memberships'); ?>
    + +
    + 0): ?> compact): ?> sortBox; ?> @@ -40,9 +44,6 @@ if ($this->hasPermission('config/authentication/users/edit') && $backend instanc compact): ?> filterEditor; ?> -
    -
    - 0): ?> @@ -80,7 +81,7 @@ if ($this->hasPermission('config/authentication/users/edit') && $backend instanc
    -

    translate('No memberships found matching the filter'); ?>

    + translate('User has no group memberships.'); ?> qlink($this->translate('Create new membership'), 'user/createmembership', array( diff --git a/public/css/icinga/main-content.less b/public/css/icinga/main-content.less index 18bc03a57..06269d271 100644 --- a/public/css/icinga/main-content.less +++ b/public/css/icinga/main-content.less @@ -236,8 +236,9 @@ div.content.users { } div.controls div.user-header { - border-bottom: 2px solid @colorPetrol; - margin-bottom: 1em; + h5 { + margin-bottom: 0em; + } .user-name { display: inline-block; @@ -302,8 +303,9 @@ div.content.groups { } div.controls div.group-header { - border-bottom: 2px solid @colorPetrol; - margin-bottom: 1em; + h5 { + margin-bottom: 0em; + } .group-name { display: inline-block; From d9f68578eb0173bebf0b34cab4dc869d68ebee31 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 8 Jun 2015 17:43:12 +0200 Subject: [PATCH 077/116] Add doc/about.md refs #9379 --- doc/about.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 doc/about.md diff --git a/doc/about.md b/doc/about.md new file mode 100644 index 000000000..6074916ab --- /dev/null +++ b/doc/about.md @@ -0,0 +1,40 @@ +# About Icinga Web 2 + +## Installation + +Icinga Web 2 can be installed easily from packages from the official package repositories. +Setting it up is also easy with the web based setup wizard. + +See [here](installation#installation) for more information about installation. + +## Configuration + +See [here](configuration#configuration) for more information about configuration. + +## Authentication + +See [here](authentication#authentication) for more information about authentication. + +## Authorization + +See [here](security#security) for more information about authorization. + +## User preferences + +See [here](preferences#preferences) for more information about user preferences. + +## Documentation + +With the documentation module you can read the documentation of the framework (and any module) directly in the user interface. + +The module can also export the documentation to PDF. + +## Translation + +With the translation module every piece of text in the user interface (of the framework itself and any module) can be translated to a language of your choise. + +Currently provided languages: + +* German +* Italian +* Portuguese From 17fa0d3f11d4f494e6456f5e5b2c6914d00deef6 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 9 Jun 2015 08:23:06 +0200 Subject: [PATCH 078/116] Revert "Improve group and user view layout" This reverts commit 44e3dba36226451afb2a3f0954ae21bc3bb6c7ba. Reason: * Controls are controls, not content * Controls must still be shown in case of a non-matching filter --- application/views/scripts/group/show.phtml | 11 +++++------ application/views/scripts/user/show.phtml | 11 +++++------ public/css/icinga/main-content.less | 10 ++++------ 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/application/views/scripts/group/show.phtml b/application/views/scripts/group/show.phtml index 8c8c91f51..636a449d4 100644 --- a/application/views/scripts/group/show.phtml +++ b/application/views/scripts/group/show.phtml @@ -30,12 +30,8 @@ if ($this->hasPermission('config/authentication/groups/edit') && $backend instan

    escape($group->group_name); ?>

    translate('Created at'); ?>: created_at === null ? '-' : $this->formatDateTime($group->created_at); ?>

    -

    translate('Last modified'); ?>: last_modified === null ? '-' : $this->formatDateTime($group->last_modified); ?>

    -
    translate('Members'); ?>
    +

    translate('Last modified'); ?>: last_modified === null ? '-' : $this->formatDateTime($group->last_modified); ?>

    -
    -
    - 0): ?> compact): ?> sortBox; ?> @@ -44,6 +40,9 @@ if ($this->hasPermission('config/authentication/groups/edit') && $backend instan compact): ?> filterEditor; ?> +
    +
    + 0): ?> @@ -67,7 +66,7 @@ if ($this->hasPermission('config/authentication/groups/edit') && $backend instan
    -

    translate('Group has no members.'); ?>

    +

    translate('No group member found matching the filter'); ?>

    qlink($this->translate('Add a new member'), 'group/addmember', array( diff --git a/application/views/scripts/user/show.phtml b/application/views/scripts/user/show.phtml index f4e9dee6a..82f4c53f9 100644 --- a/application/views/scripts/user/show.phtml +++ b/application/views/scripts/user/show.phtml @@ -30,12 +30,8 @@ if ($this->hasPermission('config/authentication/users/edit') && $backend instanc

    escape($user->user_name); ?>

    translate('State'); ?>: is_active === null ? '-' : ($user->is_active ? $this->translate('Active') : $this->translate('Inactive')); ?>

    translate('Created at'); ?>: created_at === null ? '-' : $this->formatDateTime($user->created_at); ?>

    -

    translate('Last modified'); ?>: last_modified === null ? '-' : $this->formatDateTime($user->last_modified); ?>

    -
    translate('Group Memberships'); ?>
    +

    translate('Last modified'); ?>: last_modified === null ? '-' : $this->formatDateTime($user->last_modified); ?>

    - -
    - 0): ?> compact): ?> sortBox; ?> @@ -44,6 +40,9 @@ if ($this->hasPermission('config/authentication/users/edit') && $backend instanc compact): ?> filterEditor; ?> +
    +
    + 0): ?> @@ -81,7 +80,7 @@ if ($this->hasPermission('config/authentication/users/edit') && $backend instanc
    - translate('User has no group memberships.'); ?> +

    translate('No memberships found matching the filter'); ?>

    qlink($this->translate('Create new membership'), 'user/createmembership', array( diff --git a/public/css/icinga/main-content.less b/public/css/icinga/main-content.less index 06269d271..18bc03a57 100644 --- a/public/css/icinga/main-content.less +++ b/public/css/icinga/main-content.less @@ -236,9 +236,8 @@ div.content.users { } div.controls div.user-header { - h5 { - margin-bottom: 0em; - } + border-bottom: 2px solid @colorPetrol; + margin-bottom: 1em; .user-name { display: inline-block; @@ -303,9 +302,8 @@ div.content.groups { } div.controls div.group-header { - h5 { - margin-bottom: 0em; - } + border-bottom: 2px solid @colorPetrol; + margin-bottom: 1em; .group-name { display: inline-block; From 27d2af56166c0dfdd55fdd7b683d1ad791e01b5c Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 9 Jun 2015 08:31:05 +0200 Subject: [PATCH 079/116] UserController: Allow to quick search memberships --- application/controllers/UserController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/application/controllers/UserController.php b/application/controllers/UserController.php index 5abdaece8..54482dd7d 100644 --- a/application/controllers/UserController.php +++ b/application/controllers/UserController.php @@ -103,6 +103,7 @@ class UserController extends AuthBackendController $filterEditor = Widget::create('filterEditor') ->setQuery($memberships) + ->setSearchColumns(array('group_name')) ->preserveParams('limit', 'sort', 'dir', 'view', 'backend', 'user') ->ignoreParams('page') ->handleRequest($this->getRequest()); From fb52ee5a7c1948fe6eb32395edc4ce5c2669d521 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 9 Jun 2015 08:31:21 +0200 Subject: [PATCH 080/116] GroupController: Allow to quick search members --- application/controllers/GroupController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/application/controllers/GroupController.php b/application/controllers/GroupController.php index 395b2f243..a4161981a 100644 --- a/application/controllers/GroupController.php +++ b/application/controllers/GroupController.php @@ -104,6 +104,7 @@ class GroupController extends AuthBackendController $filterEditor = Widget::create('filterEditor') ->setQuery($members) + ->setSearchColumns(array('user')) ->preserveParams('limit', 'sort', 'dir', 'view', 'backend', 'group') ->ignoreParams('page') ->handleRequest($this->getRequest()); From f2c7d5686a796c8fc3a08e5299d14da866deff40 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 9 Jun 2015 09:17:15 +0200 Subject: [PATCH 081/116] Timeline: Fix url used to extend the timeline --- .../monitoring/application/views/scripts/timeline/index.phtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/monitoring/application/views/scripts/timeline/index.phtml b/modules/monitoring/application/views/scripts/timeline/index.phtml index d0cc8d34c..209eb0f1b 100644 --- a/modules/monitoring/application/views/scripts/timeline/index.phtml +++ b/modules/monitoring/application/views/scripts/timeline/index.phtml @@ -132,8 +132,8 @@ $extrapolatedCircleWidth = $timeline->getExtrapolatedCircleWidth($timeInfo[1][$g
    About Icinga Web 2 +Icinga Web 2 is a powerful PHP framework for web applications that comes in a clean and reduced design. +It's fast, responsive, accessible and easily extensible with modules. + ## Installation Icinga Web 2 can be installed easily from packages from the official package repositories. From 5412c6d9721abc5c3f3c2519a79b42a398a7810e Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 8 Jun 2015 17:43:12 +0200 Subject: [PATCH 088/116] doc/about.md: add section `The monitoring module' refs #9379 --- doc/about.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/doc/about.md b/doc/about.md index 42cf1530f..46a0cf3c6 100644 --- a/doc/about.md +++ b/doc/about.md @@ -3,6 +3,17 @@ Icinga Web 2 is a powerful PHP framework for web applications that comes in a clean and reduced design. It's fast, responsive, accessible and easily extensible with modules. +## The monitoring module + +This is the core module for most Icinga Web 2 users. + +It provides an intuitive user interface for monitoring with Icinga (1 and 2). +Especially there are lots of list and detail views (e.g. for hosts and services) +you can sort and filter depending on what you want to see. + +You can also control the monitoring process itself by sending external commands to Icinga. +Most such actions (like rescheduling a check) can be done with just a single click. + ## Installation Icinga Web 2 can be installed easily from packages from the official package repositories. From 260a33c88b3e0313b68073949bb673581b891222 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 9 Jun 2015 14:54:50 +0200 Subject: [PATCH 089/116] Add introduction to section `Configuration' refs #9379 --- doc/about.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/about.md b/doc/about.md index 46a0cf3c6..2fbffe661 100644 --- a/doc/about.md +++ b/doc/about.md @@ -23,6 +23,8 @@ See [here](installation#installation) for more information about installation. ## Configuration +Icinga Web 2 can be configured via the user interface and the .ini files (in /etc/icingaweb2). + See [here](configuration#configuration) for more information about configuration. ## Authentication From ee7c91235a9a19e711bb8e95bb1362bdadc4f47a Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 9 Jun 2015 14:54:50 +0200 Subject: [PATCH 090/116] Add introduction to section `Authentication' refs #9379 --- doc/about.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/about.md b/doc/about.md index 2fbffe661..9fd471be6 100644 --- a/doc/about.md +++ b/doc/about.md @@ -29,7 +29,11 @@ See [here](configuration#configuration) for more information about configuration ## Authentication -See [here](authentication#authentication) for more information about authentication. +With Icinga Web 2 you can authenticate against relational databases, LDAP and more. +These authentication methods can be configured easily (via the corresponding .ini file). + +See [here](authentication#authentication) for more information about authentication +and how to configure authentication methods. ## Authorization From b8debcd257c6b37f724de6561687fbc7bb4af2ea Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 9 Jun 2015 14:54:50 +0200 Subject: [PATCH 091/116] Add introduction to section `User preferences' refs #9379 --- doc/about.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/about.md b/doc/about.md index 9fd471be6..2c9aa9aaf 100644 --- a/doc/about.md +++ b/doc/about.md @@ -41,7 +41,12 @@ See [here](security#security) for more information about authorization. ## User preferences -See [here](preferences#preferences) for more information about user preferences. +Besides the global configuration (like data/authentication backends) +each user has individual configuration options like the interface's language or the current timezone. +They can be stored either in a database or in .ini files. + +See [here](preferences#preferences) for more information about user preferences +and how to configure where to store them. ## Documentation From 10592bb7c8141a52a969715517edaa9c655dde09 Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Tue, 9 Jun 2015 16:09:36 +0200 Subject: [PATCH 092/116] Add documentation for security configuration Add a doc page to explain configuring user and group permissions and general user and group handling. refs #9377 --- doc/security.md | 211 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 doc/security.md diff --git a/doc/security.md b/doc/security.md new file mode 100644 index 000000000..071161cbd --- /dev/null +++ b/doc/security.md @@ -0,0 +1,211 @@ +# Security + +In certain situations it is useful to allow someone access to IW2, but +to prevent him from doing certain actions or from seeing specific objects. +For example, it might be a good idea, to allow the configuration of IW2 to +a certain group of administrators to prevent misconfiguration or security-breaches. +Another important use case is the creation groups of users that can only see the +fraction of hosts and services of the monitoring environment they are in charge of. + +The following chapter will describe how to do the security configuration of IW2 +and how apply permissions and restrictions to users or groups of users. + +## Basics + +IW2 has powerful tools to configure those restrictions for each **user** or **group**. +There are two general kinds of objects whose access can be managed in IW2: +**actions** and **objects**. + +### Actions + +Actions are all the things an IW2 user can do, like changing a certain configuration, +changing permissions or sending a command to the Icinga2-Core through the (Command Pipe)[http://docs.icinga.org/icinga2/latest/doc/module/icinga2/toc#!/icinga2/latest/doc/module/icinga2/chapter/getting-started#setting-up-external-command-pipe]. in the monitoring module. All actions must be be **allowed explicitly** using permissions. + +### Objects + +There are all kinds of different objects in IW2, like Hosts, Services, Notifications and many more. **By default, a user can see all objects that are available in IW2**, but it is possible to define filters to restrict what each user can see. + + +### Users + +Anyone that can **log into IW2** is considered a user and can be referenced to by the +**login name**, independently of which mechanism was used to authenticate that user. +For example, there might be user called **jdoe**, which is authenticated +using Active Directory, and one user **icingaadmin**, that is authenticated using a MySQL-Database as backend, both users can be referenced to using their the login **icingaadmin** or **jdoe** in configuration. + +IW2 users and groups are not configured by an IW2 configuration file, but provided by +an authentication backend. This means that how creating and deleting users works depends entirely on the backend. For extended information on setting up authentication backends, please read the chapter [authentication](authentication.md#authentication). + +**ATTENTION !** + +It is important to distinct between the "contacts" defined in the Icinga2-Core +configuration and the "users" that can access IW2. In this guide and for +handling security in IW2 in general only the latter ones are of importance. + + +### Groups + +If there is a big amounts of users to manage, it would be tedious to specify each user +separately when referring to a bigger group of users at once. For this reasons, it +is possible to group users in groups. + +Like with users, groups are identified solely by their unique group name, and provided +by different kind of backend called "group backends". This means, that how groups are defined depends entirely on the used group backend. For extended information on setting up group backends, please read the chapter [authentication](authentication.md#authentication). + + +TODO: example + +## Roles + +A role defines a set of **permissions** and **restrictions** in IW2, and assigns +those to users and user groups. For example, a role **admins** could define that certain +users have access to all configuration options, or another role **support** +could define that a list of users or groups is restricted to see only hosts and services +that match a specific query. + +Roles can be assigned to groups and users. In the end, the actual permission of a certain user +will be determined by the sum of all roles that are assigned to the user, or to +groups the user is part of. + + +### Configuration + +Roles can be changed either through the icingaweb2 interface, by navigation +to the page **Configuration > Authentication > Roles**, or through editing +configuration file: + + + /etc/icingaweb2/roles.ini + + +### Example + + [winadmin] + users = "johndoe, janedoe" + groups = "admin" + permissions = "config/application/*, monitoring/commands/schedule-check" + monitoring/hosts/filter = "host=*win*" + monitoring/services/filter = "host=*win*" + + + +### Syntax + +Each role is defined as a section, with the name of the role as section name. The following +attributes can be defined for each role: + + +| Key | Value | +|----------- |------------------------------------------------------------------| +| users | A comma-separated list of user **login names** that are affected by this role | +| groups | A comma-separated list of **groups names** that are affected by this role | +| permissions | A comma-separated list of permissions granted by this role | +| module/monitoring/host | A filter expression applied to all hosts | +| module/monitoring/serivce | A fuilter expression applied to all services | + + +### Permissions + +Permissions can be used to allow users or groups certain **actions**. By default, +all actions are **prohibited** and must be allowed explicitly by a role for any user. + +Each action in IW2 is denoted by a **namespaced key**, which is used to order and +group those actions. All actions that affect the configuration of IW2, are in a +namespace called **config**, while all configurations that affect authentication +are in the namespace **config/authentication** + +**Wildcards** can be used to grant permission for all actions in a certain namespace. +The permission **config/* ** would grant permission to all configuration actions, +while just specifying a wildcard ** * ** would give permission for all actions. + +When multiple roles assign permissions to the same user, either directly or indirectly +through a group, all permissions will be combined to get the users actual permission set. + +###### Global permissions + +| Keys | Permits | +|------|---------| +| * | Everything, including module-specific permissions | +| config/* | All configuration actions | +| config/application/* | Configuring IcingaWeb2 | +| config/application/general | General settings, like logging or preferences | +| config/application/resources | Change resources for retrieving data | +| config/application/userbackend | Change backends for retrieving available users | +| config/application/usergroupbackend | Change backends for retrieving available groups | +| config/authentication/* | Configure IcingaWeb2 authentication mechanisms | +| config/authentication/users/* | All user actions | +| config/authentication/users/show | Display avilable users | +| config/authentication/users/add | Add a new user to the backend | +| config/authentication/users/edit | Edit existing user in the backend | +| config/authentication/users/remove | Remove existing user from the backend | +| config/authentication/groups/* | All group actions | +| config/authentication/groups/show | Display available groups | +| config/authentication/groups/add | Add a new group to the backend | +| config/authentication/groups/edit | Edit existing group in a backend | +| config/authentication/groups/remove | Remove existing group from the backend | +| config/authentication/roles/* | All role actions | +| config/authentication/roles/add | Add a new role | +| config/authentication/roles/show | Display available roles | +| config/authentication/roles/edit | Change an existing role | +| config/authentication/roles/remove | Remove an existing row | +| config/modules | Enable or disable modules and module settings | + + +###### Monitoring module permissions + +| Keys | Permits | +|------|---------| +| monitoring/command/* | Allow all commands | +| monitoring/command/schedule-check | Allow scheduling host and service checks' | +| monitoring/command/acknowledge-problem | Allow acknowledging host and service problems | +| monitoring/command/remove-acknowledgement | Allow removing problem acknowledgements | +| monitoring/command/comment/* | Allow adding and deleting host and service comments | +| monitoring/command/comment/add | Allow commenting on hosts and services | +| monitoring/command/downtime/delete | Allow deleting host and service downtimes' | +| monitoring/command/process-check-result | Allow processing host and service check results | +| monitoring/command/feature/instance | Allow processing commands for toggling features on an instance-wide basis | +| monitoring/command/feature/object | Allow processing commands for toggling features on host and service objects | +| monitoring/command/send-custom-notification | Allow sending custom notifications for hosts and services | + +### Restrictions + +Restrictions can be used to define what a user or group can see, by specifying +a filter expression. This filter expression will be **implicitly** added as an AND-Clause +to the used filter expression. + +Any URL filter **?foo=bar&baz=bar** in IW2, would therefore implicitly +become ** ?(foo=bar&baz=bar)&( $RESTRICTION_FILTER )**, depending on the users current restrictions. + +##### Filter Expressions + +Any filter expression that is allowed in + + +###### Monitoring module filters + +Restrictions can apply to services, hosts or custom keys defined by any module. +The monitoring module provides the following restrictions: + +| Keys | Filters | +|------|---------| +| monitoring/hosts/filter | Restrict hosts view to the hosts that match the filter | +| monitoring/services/filter | Restrict services view to the services that match the filter | + +Restrictions on hosts or services will automatically apply to all notifications, events +or downtimes that belong to those objects. + +** ATTENTION ! ** + +Specifying a host filter will **not** automatically apply to all services on that host. If this behavior is desired, it is always necessary to filter the host for those services too. + +## Modules + +When creating IW2 modules, it might be necessary to define new actions that can be granted or revoked to certain users. One example for this, is the monitoring module, that is the heart of the monitoring functionality of IW2. + +## Troubleshooting + +Q: I cannot see the menu for changing roles in Icinga Web 2 + +A: Your user lacks the permission to change the authentication configuration, + add a new role in roles.ini that gives your current user + the permission to access **config/authentication/* ** From 85b3eaa2e3a041a32274efcf791e1de83ec3faee Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 9 Jun 2015 14:54:50 +0200 Subject: [PATCH 093/116] Add introduction to section `Authorization' refs #9379 --- doc/about.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/about.md b/doc/about.md index 2c9aa9aaf..1a0135d03 100644 --- a/doc/about.md +++ b/doc/about.md @@ -37,7 +37,12 @@ and how to configure authentication methods. ## Authorization -See [here](security#security) for more information about authorization. +In Icinga Web 2 there are permissions and restrictions to allow and deny (respectively) +roles to view or to do certain things. +These roles can be assigned to users and groups. + +See [here](security#security) for more information about authorization +and how to configure roles. ## User preferences From 7cf0b82b63bce18815157dab0bd477e3ec768c08 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 8 Jun 2015 17:43:12 +0200 Subject: [PATCH 094/116] doc/about.md: typos and grammar refs #9379 --- doc/about.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/doc/about.md b/doc/about.md index 1a0135d03..48b4041ac 100644 --- a/doc/about.md +++ b/doc/about.md @@ -19,21 +19,21 @@ Most such actions (like rescheduling a check) can be done with just a single cli Icinga Web 2 can be installed easily from packages from the official package repositories. Setting it up is also easy with the web based setup wizard. -See [here](installation#installation) for more information about installation. +See [here](installation#installation) for more information about the installation. ## Configuration -Icinga Web 2 can be configured via the user interface and the .ini files (in /etc/icingaweb2). +Icinga Web 2 can be configured via the user interface and .ini files. -See [here](configuration#configuration) for more information about configuration. +See [here](configuration#configuration) for more information about the configuration. ## Authentication With Icinga Web 2 you can authenticate against relational databases, LDAP and more. -These authentication methods can be configured easily (via the corresponding .ini file). +These authentication methods can be easily configured (via the corresponding .ini file). -See [here](authentication#authentication) for more information about authentication -and how to configure authentication methods. +See [here](authentication#authentication) for more information about +the different authentication methods available and how to configure them. ## Authorization @@ -46,12 +46,12 @@ and how to configure roles. ## User preferences -Besides the global configuration (like data/authentication backends) -each user has individual configuration options like the interface's language or the current timezone. +Besides the global configuration each user has individual configuration options +like the interface's language or the current timezone. They can be stored either in a database or in .ini files. -See [here](preferences#preferences) for more information about user preferences -and how to configure where to store them. +See [here](preferences#preferences) for more information about a user's preferences +and how to configure their storage type. ## Documentation @@ -61,7 +61,7 @@ The module can also export the documentation to PDF. ## Translation -With the translation module every piece of text in the user interface (of the framework itself and any module) can be translated to a language of your choise. +With the translation module every piece of text in the user interface (of the framework itself and any module) can be translated to a language of your choice. Currently provided languages: From 6f208e90b54ee2c31b8449cc484ba5e73911531c Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Tue, 9 Jun 2015 18:10:02 +0200 Subject: [PATCH 095/116] Improve restriction and filter sections Add more examples and fix a few typos and minor issues in the doc. refs #9377 --- doc/security.md | 97 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 74 insertions(+), 23 deletions(-) diff --git a/doc/security.md b/doc/security.md index 071161cbd..6652813b2 100644 --- a/doc/security.md +++ b/doc/security.md @@ -12,14 +12,14 @@ and how apply permissions and restrictions to users or groups of users. ## Basics -IW2 has powerful tools to configure those restrictions for each **user** or **group**. +IW2 permissions are managed by defining **roles** that grant permissions or restrictions to **users** and **group**. There are two general kinds of objects whose access can be managed in IW2: **actions** and **objects**. ### Actions Actions are all the things an IW2 user can do, like changing a certain configuration, -changing permissions or sending a command to the Icinga2-Core through the (Command Pipe)[http://docs.icinga.org/icinga2/latest/doc/module/icinga2/toc#!/icinga2/latest/doc/module/icinga2/chapter/getting-started#setting-up-external-command-pipe]. in the monitoring module. All actions must be be **allowed explicitly** using permissions. +changing permissions or sending a command to the Icinga2-Core through the Command Pipe in the monitoring module. All actions must be be **allowed explicitly** using permissions. ### Objects @@ -28,7 +28,7 @@ There are all kinds of different objects in IW2, like Hosts, Services, Notificat ### Users -Anyone that can **log into IW2** is considered a user and can be referenced to by the +Anyone that can **login** to IW2 is considered a user and can be referenced to by the **login name**, independently of which mechanism was used to authenticate that user. For example, there might be user called **jdoe**, which is authenticated using Active Directory, and one user **icingaadmin**, that is authenticated using a MySQL-Database as backend, both users can be referenced to using their the login **icingaadmin** or **jdoe** in configuration. @@ -49,8 +49,7 @@ If there is a big amounts of users to manage, it would be tedious to specify eac separately when referring to a bigger group of users at once. For this reasons, it is possible to group users in groups. -Like with users, groups are identified solely by their unique group name, and provided -by different kind of backend called "group backends". This means, that how groups are defined depends entirely on the used group backend. For extended information on setting up group backends, please read the chapter [authentication](authentication.md#authentication). +Like with users, groups are identified solely by their **name**, and provided by backends called **group backends**. Like with users, it depends entirely on the used backend how groups are created and managed. For extended information on setting up group backends, please read the chapter [authentication](authentication.md#authentication). TODO: example @@ -80,14 +79,19 @@ configuration file: ### Example + [winadmin] - users = "johndoe, janedoe" + users = "jdoe, janedoe" groups = "admin" permissions = "config/application/*, monitoring/commands/schedule-check" monitoring/hosts/filter = "host=*win*" monitoring/services/filter = "host=*win*" +This example creates a role called **winadmin**, that grants the permission **config/application/* ** and ** monitoring/commands/schedule-check ** and additonally only +allows the hosts and services that match the filter ** host=\*win* ** to be displayed. The users +**jdoe** and **janedoe** and all members of the group **admin** will be affected +by this role. ### Syntax @@ -167,40 +171,87 @@ through a group, all permissions will be combined to get the users actual permis | monitoring/command/feature/object | Allow processing commands for toggling features on host and service objects | | monitoring/command/send-custom-notification | Allow sending custom notifications for hosts and services | + + ### Restrictions Restrictions can be used to define what a user or group can see, by specifying -a filter expression. This filter expression will be **implicitly** added as an AND-Clause -to the used filter expression. +a filter expression. By default, when no filter is defined, a user will be able +to see every object available. When a filter is specified for a certain object type +the user will only be able to see objects applied -Any URL filter **?foo=bar&baz=bar** in IW2, would therefore implicitly -become ** ?(foo=bar&baz=bar)&( $RESTRICTION_FILTER )**, depending on the users current restrictions. +The filter expression will be **implicitly** added as an **AND-Clause** +to each query. Any URL filter **?foo=bar&baz=bar** in IW2, would therefore implicitly +become ** ?(foo=bar&baz=bar)&($FILTER)**, depending on the users current restrictions. -##### Filter Expressions - -Any filter expression that is allowed in +When multiple roles assign restrictions to the same user, either directly or indirectly +through a group, all filters will be combined using an **AND-Clause**, resulting in the final +expression ** ?(foo=bar&baz=bar)&$ FILTER1 & $FILTER2 & $FILTER3** appended to each query. -###### Monitoring module filters +##### Monitoring module filters -Restrictions can apply to services, hosts or custom keys defined by any module. -The monitoring module provides the following restrictions: +The monitoring module provides **hosts** and **services** as filterable objects: -| Keys | Filters | -|------|---------| -| monitoring/hosts/filter | Restrict hosts view to the hosts that match the filter | -| monitoring/services/filter | Restrict services view to the services that match the filter | -Restrictions on hosts or services will automatically apply to all notifications, events +| Keys | Restricts | +|----------------------------|--------------------------------------------------| +| monitoring/hosts/filter | Only display **hosts** that match this filter | +| monitoring/services/filter | Only display **services** that match this filter | + + +Filters on hosts or services will automatically apply to all notifications, events or downtimes that belong to those objects. + ** ATTENTION ! ** -Specifying a host filter will **not** automatically apply to all services on that host. If this behavior is desired, it is always necessary to filter the host for those services too. +Unlike with notifications or downtimes, a host filter will **not** automatically apply to all services on that host. If this behavior is desired, it is always necessary to add the +filter expression to both, **services and hosts**. + + +##### Filter Expressions + +Any filter expression that is allowed in the filtered view, is also an allowed filter expression. +This means, that it is possible to define negations, wildcards, and even nested +filter expressions containing AND and OR-Clauses. + +The following examples will show some examples for more complex filters: + +###### Example 1: Negation + + [onlywin] + groups = "windows-admins" + monitoring/hosts/filter = "host=*win*" + monitoring/services/filter = "host=*win*" + +Will display only hosts and services whose hosts contains the **win**. + + [nowin] + groups = "unix-admins" + monitoring/hosts/filter = "host!=*win*" + monitoring/services/filter = "host!=*win*" + +Will only match hosts and services whose host does **not** contain **win** + +Also notice that if a user that is member of both groups, **windows-admins** and **unix-admins**, +he wont be able to see any hosts, as both filters will be applied and remove any object from the query. + + +###### Example 2: Hostgroups + + [only-unix] + groups = "unix-admins" + monitoring/hosts/filter = "(hostgroup_name=bsd-servers|hostgroup_name=linux-servers)" + monitoring/services/filter = "(hostgroup_name=bsd-servers|hostgroup_name=linux-servers)" + + +This role allows all members of the group unix-admins to only see hosts and services +that are part of the host-group linux-servers or the host-group bsd-servers. ## Modules -When creating IW2 modules, it might be necessary to define new actions that can be granted or revoked to certain users. One example for this, is the monitoring module, that is the heart of the monitoring functionality of IW2. +When creating IW2 modules, it might be necessary to define new actions that can be granted or revoked to certain users. One example for this, is the monitoring module, that is the heart of the monitoring functionality of IW2.while ## Troubleshooting From fa75243f02239fd3ee78d69294b27563a30bee09 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 9 Jun 2015 18:16:36 +0200 Subject: [PATCH 096/116] RPM: call usermod w/ valid options on openSUSE fixes #9378 --- icingaweb2.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/icingaweb2.spec b/icingaweb2.spec index c7aa4cb8d..84c3e0b13 100644 --- a/icingaweb2.spec +++ b/icingaweb2.spec @@ -201,7 +201,7 @@ cp -prv packages/files/config/modules/setup %{buildroot}/%{configdir}/modules/ %pre getent group icingacmd >/dev/null || groupadd -r icingacmd -%if 0%{?suse_version} +%if 0%{?suse_version} && 0%{?suse_version} < 01200 usermod -A icingacmd,%{icingawebgroup} %{wwwuser} %else usermod -a -G icingacmd,%{icingawebgroup} %{wwwuser} From e764bfb95dc540ec41bde6a779e6c652e1cf88e8 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 10 Jun 2015 14:29:44 +0200 Subject: [PATCH 097/116] Repair command links fixes #9392 --- .../views/scripts/show/components/notifications.phtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/monitoring/application/views/scripts/show/components/notifications.phtml b/modules/monitoring/application/views/scripts/show/components/notifications.phtml index 5fea60cc1..9a9195862 100644 --- a/modules/monitoring/application/views/scripts/show/components/notifications.phtml +++ b/modules/monitoring/application/views/scripts/show/components/notifications.phtml @@ -9,7 +9,7 @@ echo $this->qlink( $this->translate('Send notification'), 'monitoring/host/send-custom-notification', - array('host_name' => $object->getName()), + array('host' => $object->getName()), array( 'icon' => 'bell', 'data-base-target' => '_self', @@ -23,7 +23,7 @@ echo $this->qlink( $this->translate('Send notification'), 'monitoring/service/send-custom-notification', - array('host_name' => $object->getHost()->getName(), 'service_description' => $object->getName()), + array('host' => $object->getHost()->getName(), 'service' => $object->getName()), array( 'icon' => 'bell', 'data-base-target' => '_self', From 392047b2eeba64eab151f378accc510310bb521f Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 10 Jun 2015 17:19:49 +0200 Subject: [PATCH 098/116] Add support for command line arguments in the format --arg= refs #8472 --- library/Icinga/Cli/Params.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Cli/Params.php b/library/Icinga/Cli/Params.php index 9e30cf19e..85d5cd26f 100644 --- a/library/Icinga/Cli/Params.php +++ b/library/Icinga/Cli/Params.php @@ -47,7 +47,12 @@ class Params $noOptionFlag = true; } elseif (!$noOptionFlag && substr($argv[$i], 0, 2) === '--') { $key = substr($argv[$i], 2); - if (! isset($argv[$i + 1]) || substr($argv[$i + 1], 0, 2) === '--') { + $matches = array(); + if (1 === preg_match( + '/(?params[$matches[1]] = $matches[2]; + } elseif (! isset($argv[$i + 1]) || substr($argv[$i + 1], 0, 2) === '--') { $this->params[$key] = true; } elseif (array_key_exists($key, $this->params)) { if (!is_array($this->params[$key])) { From 98b2699e354fe32c38d091b63b5ea2c14b9e27a5 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 10 Jun 2015 17:20:27 +0200 Subject: [PATCH 099/116] Documentation: use command line arguments in the format --arg= refs #8472 --- .../application/clicommands/ConferenceCommand.php | 2 +- .../monitoring/application/clicommands/ListCommand.php | 10 +++++----- .../monitoring/application/clicommands/NrpeCommand.php | 4 ++-- .../setup/application/clicommands/ConfigCommand.php | 8 ++++---- modules/test/application/clicommands/PhpCommand.php | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/modules/monitoring/application/clicommands/ConferenceCommand.php b/modules/monitoring/application/clicommands/ConferenceCommand.php index c7c7f1292..d0062b1a9 100644 --- a/modules/monitoring/application/clicommands/ConferenceCommand.php +++ b/modules/monitoring/application/clicommands/ConferenceCommand.php @@ -20,7 +20,7 @@ class ConferenceCommand extends Command * Use this command in case you feel that you should be friendly. Should * be executed as follows: * - * icingacli monitoring conference welcome --watch 1 + * icingacli monitoring conference welcome --watch=1 */ public function welcomeAction() { diff --git a/modules/monitoring/application/clicommands/ListCommand.php b/modules/monitoring/application/clicommands/ListCommand.php index 9fd892652..d9ecb0c9a 100644 --- a/modules/monitoring/application/clicommands/ListCommand.php +++ b/modules/monitoring/application/clicommands/ListCommand.php @@ -124,19 +124,19 @@ class ListCommand extends Command * --verbose Show detailled output * --showsql Dump generated SQL query (DB backend only) * - * --format > + * --format=> * Dump columns in the given format. format allows $column$ - * placeholders, e.g. --format '$host$: $service$' + * placeholders, e.g. --format='$host$: $service$' * - * -- [filter] + * --[=filter] * Filter given column by optional filter. Boolean (1/0) columns are * true if no filter value is given. * * EXAMPLES * * icingacli monitoring list --unhandled - * icingacli monitoring list --host local* --service *disk* - * icingacli monitoring list --format '$host$: $service$' + * icingacli monitoring list --host=local* --service=*disk* + * icingacli monitoring list --format='$host$: $service$' */ public function statusAction() { diff --git a/modules/monitoring/application/clicommands/NrpeCommand.php b/modules/monitoring/application/clicommands/NrpeCommand.php index 190cdbe85..abb877553 100644 --- a/modules/monitoring/application/clicommands/NrpeCommand.php +++ b/modules/monitoring/application/clicommands/NrpeCommand.php @@ -27,8 +27,8 @@ class NrpeCommand extends Command * * EXAMPLE * - * icingacli monitoring nrpe 127.0.0.1 CheckMEM --ssl --MaxWarn 80% \ - * --MaxCrit 90% --type physical + * icingacli monitoring nrpe 127.0.0.1 CheckMEM --ssl --MaxWarn=80% \ + * --MaxCrit=90% --type=physical */ public function checkAction() { diff --git a/modules/setup/application/clicommands/ConfigCommand.php b/modules/setup/application/clicommands/ConfigCommand.php index eb5aaca83..a4a5da12e 100644 --- a/modules/setup/application/clicommands/ConfigCommand.php +++ b/modules/setup/application/clicommands/ConfigCommand.php @@ -29,7 +29,7 @@ class ConfigCommand extends Command * * icingacli setup config directory * - * icingacli setup config directory --mode 2775 --config /opt/icingaweb2/etc + * icingacli setup config directory --mode=2775 --config=/opt/icingaweb2/etc */ public function directoryAction() { @@ -96,7 +96,7 @@ class ConfigCommand extends Command * * --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] + * --root|--document-root= The directory from which the webserver will serve files [/path/to/icingaweb2/public] * * --config= Path to Icinga Web 2's configuration files [/etc/icingaweb2] * @@ -106,9 +106,9 @@ class ConfigCommand extends Command * * 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 --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 apache --file=/etc/apache2/conf.d/icingaweb2.conf * * icingacli setup config webserver nginx */ diff --git a/modules/test/application/clicommands/PhpCommand.php b/modules/test/application/clicommands/PhpCommand.php index c84000f70..59a79e0a1 100644 --- a/modules/test/application/clicommands/PhpCommand.php +++ b/modules/test/application/clicommands/PhpCommand.php @@ -41,7 +41,7 @@ class PhpCommand extends Command * * icingacli test php unit --verbose * icingacli test php unit --build - * icingacli test php unit --include *SpecialTest + * icingacli test php unit --include=*SpecialTest */ public function unitAction() { @@ -109,8 +109,8 @@ class PhpCommand extends Command * * icingacli test php style --verbose * icingacli test php style --build - * icingacli test php style --include path/to/your/file - * icingacli test php style --exclude *someFile* --exclude someOtherFile* + * icingacli test php style --include=path/to/your/file + * icingacli test php style --exclude=*someFile* --exclude=someOtherFile* */ public function styleAction() { From a9ea6a7c9a83116671b52176c17d144ffac1de13 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 10 Jun 2015 17:50:37 +0200 Subject: [PATCH 100/116] Don't use non-existent Format::timeSince() fixes #9400 --- modules/monitoring/application/clicommands/ListCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/monitoring/application/clicommands/ListCommand.php b/modules/monitoring/application/clicommands/ListCommand.php index d9ecb0c9a..59a98ddf2 100644 --- a/modules/monitoring/application/clicommands/ListCommand.php +++ b/modules/monitoring/application/clicommands/ListCommand.php @@ -5,7 +5,7 @@ namespace Icinga\Module\Monitoring\Clicommands; use Icinga\Module\Monitoring\Backend; use Icinga\Module\Monitoring\Cli\CliUtils; -use Icinga\Util\Format; +use Icinga\Date\DateFormatter; use Icinga\Cli\Command; use Icinga\File\Csv; use Icinga\Module\Monitoring\Plugin\PerfdataSet; @@ -299,7 +299,7 @@ class ListCommand extends Command $leaf, $screen->underline($row->service_description), $screen->colorize($utils->objectStateFlags('service', $row) . $perf, 'lightblue'), - ucfirst(Format::timeSince($row->service_last_state_change)) + ucfirst(DateFormatter::timeSince($row->service_last_state_change)) ); if ($this->isVerbose) { $out .= $emptyLine . preg_replace( From edd1c2a1f3038fc03cc39783f9bc51cde3964f2c Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Wed, 10 Jun 2015 18:43:54 +0200 Subject: [PATCH 101/116] Move monitoring permission documentation into a separate module refs #9377 --- doc/security.md | 326 ++++++++++++++++++++---------------- modules/doc/doc/security.md | 65 +++++++ 2 files changed, 242 insertions(+), 149 deletions(-) create mode 100644 modules/doc/doc/security.md diff --git a/doc/security.md b/doc/security.md index 6652813b2..1b61f8ab3 100644 --- a/doc/security.md +++ b/doc/security.md @@ -1,258 +1,286 @@ # Security -In certain situations it is useful to allow someone access to IW2, but -to prevent him from doing certain actions or from seeing specific objects. -For example, it might be a good idea, to allow the configuration of IW2 to -a certain group of administrators to prevent misconfiguration or security-breaches. -Another important use case is the creation groups of users that can only see the -fraction of hosts and services of the monitoring environment they are in charge of. +Access control is a vital part of configuring Icinga Web 2 in a secure way. +It is important that not every user that has access to Icinga Web 2 is able +to do any action or see any host and service. For example, it is useful to allow +only a small group of administrators to change the Icinga Web 2 configuration, to prevent misconfiguration or security breaches. Another important use case is the creation groups of +users that can only see the fraction of hosts and services of the monitoring + environment they are in charge of. -The following chapter will describe how to do the security configuration of IW2 +This chapter will describe how to do the security configuration of Icinga Web 2 and how apply permissions and restrictions to users or groups of users. ## Basics -IW2 permissions are managed by defining **roles** that grant permissions or restrictions to **users** and **group**. -There are two general kinds of objects whose access can be managed in IW2: -**actions** and **objects**. +Icinga Web 2 access control is done by defining **roles** that relate permissions or restrictions to **users** and **group**. +There are two general kinds of things to which access can be managed: actions and objects. + ### Actions -Actions are all the things an IW2 user can do, like changing a certain configuration, -changing permissions or sending a command to the Icinga2-Core through the Command Pipe in the monitoring module. All actions must be be **allowed explicitly** using permissions. +Actions are all the things an Icinga Web 2 user can do, like changing a certain configuration, +changing permissions or sending a command to the Icinga instance through the Command Pipe in the monitoring module. All actions must be be **allowed explicitly** using permissions. + +A permission is a simple list of identifiers of actions a user is +allowed to do. Permissions are described in greater detail in the +section [Permissions](#permissions). ### Objects -There are all kinds of different objects in IW2, like Hosts, Services, Notifications and many more. **By default, a user can see all objects that are available in IW2**, but it is possible to define filters to restrict what each user can see. +There are all kinds of different objects in Icinga Web 2: Hosts, Services, Notifications, Downtimes and Events. +By default, a user can **see everything**, but it is possible to **explicitly restrict** what each user can see using Restrictions. + +Restrictions are complex filter queries that describe what objects should be displayed to a user. Restrictions are described +in greater detail in the section [Restrictions](#restrictions). ### Users -Anyone that can **login** to IW2 is considered a user and can be referenced to by the -**login name**, independently of which mechanism was used to authenticate that user. -For example, there might be user called **jdoe**, which is authenticated -using Active Directory, and one user **icingaadmin**, that is authenticated using a MySQL-Database as backend, both users can be referenced to using their the login **icingaadmin** or **jdoe** in configuration. +Anyone who can **login** to Icinga Web 2 is considered a user and can be referenced to by the +**user name** used during login. +For example, there might be user called **jdoe** authenticated +using Active Directory, and a user **icingaadmin** that is authenticated using a MySQL-Database as backend. Both can be referenced by using their the user names **icingaadmin** or **jdoe**, when referring to those users in the configuration. -IW2 users and groups are not configured by an IW2 configuration file, but provided by -an authentication backend. This means that how creating and deleting users works depends entirely on the backend. For extended information on setting up authentication backends, please read the chapter [authentication](authentication.md#authentication). +Icinga Web 2 users and groups are not configured by a configuration file, but provided by +an **authentication backend**. For extended information on setting up authentication backends and managing users, please read the chapter [authentication](authentication.md#authentication). -**ATTENTION !** -It is important to distinct between the "contacts" defined in the Icinga2-Core -configuration and the "users" that can access IW2. In this guide and for -handling security in IW2 in general only the latter ones are of importance. +
    + Since Icinga Web 2, users in the Icinga configuration and the web authentication are separated, to allow + use of external authentication providers. This means that users and groups defined in the Icinga configuration are not available to Icinga Web 2. Instead it uses its own authentication + backend to fetch users and groups from, which must be configured separately. +
    +#### Managing Users + +When using a [Database +as authentication backend](authentication.md#authentication-configuration-db-authentication), it is possible to create, add and delete users directly in the frontend. This configuration +can be found at **Configuration > Authentication > Users **. ### Groups If there is a big amounts of users to manage, it would be tedious to specify each user -separately when referring to a bigger group of users at once. For this reasons, it -is possible to group users in groups. +separately when referring to a bigger group of users at once. Because of that, any user can be a member of multiple groups, and will inherit all permissions and restrictions. -Like with users, groups are identified solely by their **name**, and provided by backends called **group backends**. Like with users, it depends entirely on the used backend how groups are created and managed. For extended information on setting up group backends, please read the chapter [authentication](authentication.md#authentication). +Like users, groups are identified solely by their **name** that is provided by + a **group backend**. For extended information on setting up group backends, + please read the chapter [authentication](authentication.md#authentication). -TODO: example +#### Managing Groups + +When using a [Database as an authentication backend](#authentication.md#authentication-configuration-db-authentication), +it is possible manage groups and group memberships directly in the frontend. This configuration +can be found at **Configuration > Authentication > Groups **. ## Roles -A role defines a set of **permissions** and **restrictions** in IW2, and assigns -those to users and user groups. For example, a role **admins** could define that certain +A role defines a set of **permissions** and **restrictions** and assigns +those to **users** and **groups**. For example, a role **admins** could define that certain users have access to all configuration options, or another role **support** could define that a list of users or groups is restricted to see only hosts and services that match a specific query. -Roles can be assigned to groups and users. In the end, the actual permission of a certain user -will be determined by the sum of all roles that are assigned to the user, or to -groups the user is part of. - +The actual permission of a certain user will be determined by merging the permissions and restrictions of the user itself and all the groups the user is part of. Permissions can +be simply added up, while restrictions follow a slighty more complex pattern, that is described +in the section [Stacking Filters](#stacking-filters). ### Configuration Roles can be changed either through the icingaweb2 interface, by navigation -to the page **Configuration > Authentication > Roles**, or through editing +to the page **Configuration > Authentication > Roles**, or through editing the configuration file: /etc/icingaweb2/roles.ini -### Example +#### Introducing Example + +To get you a quick start, here is an example of how a role definition could look like: [winadmin] users = "jdoe, janedoe" groups = "admin" permissions = "config/application/*, monitoring/commands/schedule-check" - monitoring/hosts/filter = "host=*win*" - monitoring/services/filter = "host=*win*" + monitoring/filter/objects = "host=*win*" -This example creates a role called **winadmin**, that grants the permission **config/application/* ** and ** monitoring/commands/schedule-check ** and additonally only +This example creates a role called **winadmin**, that grants all permissions in **config/application/** and ** monitoring/commands/schedule-check ** and additionally only allows the hosts and services that match the filter ** host=\*win* ** to be displayed. The users **jdoe** and **janedoe** and all members of the group **admin** will be affected by this role. -### Syntax + +#### Syntax Each role is defined as a section, with the name of the role as section name. The following -attributes can be defined for each role: +attributes can be defined for each role in a default Icinga Web 2 installation: -| Key | Value | -|----------- |------------------------------------------------------------------| -| users | A comma-separated list of user **login names** that are affected by this role | -| groups | A comma-separated list of **groups names** that are affected by this role | -| permissions | A comma-separated list of permissions granted by this role | -| module/monitoring/host | A filter expression applied to all hosts | -| module/monitoring/serivce | A fuilter expression applied to all services | +| Key | Value | +|---------------------------|---------------------------------------------------------------------------------| +| users | A comma-separated list of user **user names** that are affected by this role | +| groups | A comma-separated list of **group names** that are affected by this role | +| permissions | A comma-separated list of **permissions** granted by this role | +| monitoring/filter/objects | A **filter expression** that restricts the access to services and hosts | -### Permissions + +## Permissions Permissions can be used to allow users or groups certain **actions**. By default, all actions are **prohibited** and must be allowed explicitly by a role for any user. -Each action in IW2 is denoted by a **namespaced key**, which is used to order and -group those actions. All actions that affect the configuration of IW2, are in a +Each action in Icinga Web 2 is denoted by a **namespaced key**, which is used to order and +group those actions. All actions that affect the configuration of Icinga Web 2, are in a namespace called **config**, while all configurations that affect authentication are in the namespace **config/authentication** **Wildcards** can be used to grant permission for all actions in a certain namespace. -The permission **config/* ** would grant permission to all configuration actions, +The permission **config/ ** would grant permission to all configuration actions, while just specifying a wildcard ** * ** would give permission for all actions. When multiple roles assign permissions to the same user, either directly or indirectly through a group, all permissions will be combined to get the users actual permission set. -###### Global permissions +#### Global permissions -| Keys | Permits | -|------|---------| -| * | Everything, including module-specific permissions | -| config/* | All configuration actions | -| config/application/* | Configuring IcingaWeb2 | -| config/application/general | General settings, like logging or preferences | -| config/application/resources | Change resources for retrieving data | -| config/application/userbackend | Change backends for retrieving available users | -| config/application/usergroupbackend | Change backends for retrieving available groups | -| config/authentication/* | Configure IcingaWeb2 authentication mechanisms | -| config/authentication/users/* | All user actions | -| config/authentication/users/show | Display avilable users | -| config/authentication/users/add | Add a new user to the backend | -| config/authentication/users/edit | Edit existing user in the backend | -| config/authentication/users/remove | Remove existing user from the backend | -| config/authentication/groups/* | All group actions | -| config/authentication/groups/show | Display available groups | -| config/authentication/groups/add | Add a new group to the backend | -| config/authentication/groups/edit | Edit existing group in a backend | -| config/authentication/groups/remove | Remove existing group from the backend | -| config/authentication/roles/* | All role actions | -| config/authentication/roles/add | Add a new role | -| config/authentication/roles/show | Display available roles | -| config/authentication/roles/edit | Change an existing role | -| config/authentication/roles/remove | Remove an existing row | -| config/modules | Enable or disable modules and module settings | +| Name | Permits | +|-------------------------------------|-----------------------------------------------------------------| +| * | Allow everything, including module-specific permissions | +| config/* | Allow all configuration actions | +| config/application/* | Allow configuring IcingaWeb2 | +| config/application/general | Allow general settings, like logging or preferences | +| config/application/resources | Allow changing resources for retrieving data | +| config/application/userbackend | Allow changing backends for retrieving available users | +| config/application/usergroupbackend | Allow changing backends for retrieving available groups | +| config/authentication/* | Allow configuring IcingaWeb2 authentication mechanisms | +| config/authentication/users/* | Allow all user actions | +| config/authentication/users/show | Allow displaying avilable users | +| config/authentication/users/add | Allow adding a new user to the backend | +| config/authentication/users/edit | Allow editing an existing user in the backend | +| config/authentication/users/remove | Allow removing an existing user from the backend | +| config/authentication/groups/* | Allow all group actions | +| config/authentication/groups/show | Allow displaying all available groups | +| config/authentication/groups/add | Allow adding a new group to the backend | +| config/authentication/groups/edit | Allow editing existing groups in a backend | +| config/authentication/groups/remove | Allow removing existing groups from the backend | +| config/authentication/roles/* | Allow all role actions | +| config/authentication/roles/add | Allow adding a new role | +| config/authentication/roles/show | Allow displaying available roles | +| config/authentication/roles/edit | Allow changing an existing role | +| config/authentication/roles/remove | Allow removing an existing row | +| config/modules | Allow enabling or disable modules and module changing settings | -###### Monitoring module permissions +#### Monitoring module permissions -| Keys | Permits | -|------|---------| -| monitoring/command/* | Allow all commands | -| monitoring/command/schedule-check | Allow scheduling host and service checks' | -| monitoring/command/acknowledge-problem | Allow acknowledging host and service problems | -| monitoring/command/remove-acknowledgement | Allow removing problem acknowledgements | -| monitoring/command/comment/* | Allow adding and deleting host and service comments | -| monitoring/command/comment/add | Allow commenting on hosts and services | -| monitoring/command/downtime/delete | Allow deleting host and service downtimes' | -| monitoring/command/process-check-result | Allow processing host and service check results | -| monitoring/command/feature/instance | Allow processing commands for toggling features on an instance-wide basis | -| monitoring/command/feature/object | Allow processing commands for toggling features on host and service objects | -| monitoring/command/send-custom-notification | Allow sending custom notifications for hosts and services | +The built-in monitoring module defines an additional set of permissions, that +are described in detail in [monitoring module documentation](/icingaweb2/doc/module/doc/chapter/monitoring-security#monitoring-security). -### Restrictions +## Restrictions -Restrictions can be used to define what a user or group can see, by specifying -a filter expression. By default, when no filter is defined, a user will be able -to see every object available. When a filter is specified for a certain object type -the user will only be able to see objects applied - -The filter expression will be **implicitly** added as an **AND-Clause** -to each query. Any URL filter **?foo=bar&baz=bar** in IW2, would therefore implicitly -become ** ?(foo=bar&baz=bar)&($FILTER)**, depending on the users current restrictions. - -When multiple roles assign restrictions to the same user, either directly or indirectly -through a group, all filters will be combined using an **AND-Clause**, resulting in the final -expression ** ?(foo=bar&baz=bar)&$ FILTER1 & $FILTER2 & $FILTER3** appended to each query. +Restrictions can be used to define what a user or group can see by specifying +a filter expression. By default, when no restrictions are defined, a user will be able +to see every object available. When a restriction is specified for a certain object type +the user will only be able to see objects applied. -##### Monitoring module filters +### Filterable Objects -The monitoring module provides **hosts** and **services** as filterable objects: +Each filter expression consists of two parts: The **key** that defines **which** objects +to filter and the filter, that defines **what** to filter. + +In a default Icinga Web 2 installations, the only objects that are filterable +are the objects provided by the monitoring module. The monitoring module provides +**hosts** and **services** as filterable objects: -| Keys | Restricts | -|----------------------------|--------------------------------------------------| -| monitoring/hosts/filter | Only display **hosts** that match this filter | -| monitoring/services/filter | Only display **services** that match this filter | +| Keys | Restricts | +|----------------------------|-----------------------------------------------| +| monitoring/filter/objects | Applies host and service filters to all hosts | -Filters on hosts or services will automatically apply to all notifications, events -or downtimes that belong to those objects. +A complete list of all available filter columns can be found in +the [monitoring module documentation](/icingaweb2/doc/module/doc/chapter/monitoring-security#monitoring-security-restrictions). - -** ATTENTION ! ** - -Unlike with notifications or downtimes, a host filter will **not** automatically apply to all services on that host. If this behavior is desired, it is always necessary to add the -filter expression to both, **services and hosts**. - - -##### Filter Expressions +### Filter Expressions Any filter expression that is allowed in the filtered view, is also an allowed filter expression. This means, that it is possible to define negations, wildcards, and even nested filter expressions containing AND and OR-Clauses. -The following examples will show some examples for more complex filters: +The filter expression will be **implicitly** added as an **AND-Clause** to each query on the filtered objects. The following shows the filter expression **host = \*win* ** being applied on ** monitoring/filter/objects **. -###### Example 1: Negation - [onlywin] +Regular filter query: + + AND-- service_problem = 1 + | + +--- service_handled = 0 + + +With our restriction applied, any user affected by this restrictions will see the +results of this query instead: + + + AND-- host = *win* + | + +--AND-- service_problem = 1 + | + +--- service_handled = 0 + + +#### Stacking Filters + +When multiple roles assign restrictions to the same user, either directly or indirectly +through a group, all filters will be combined using an **OR-Clause**, resulting in the final +expression: + + + AND -- OR-- $FILTER1 + | | + | +-- $FILTER2 + | | + | +-- $FILTER3 + | + +--AND-- service_problem = 1 + | + +--- service_handled = 0 + + +As a result, a user is be able to see hosts that are matched by **ANY** of +the filter expressions. The following examples will show the usefulness of this behavior: + +#### Example 1: Negation + + [winadmin] groups = "windows-admins" - monitoring/hosts/filter = "host=*win*" - monitoring/services/filter = "host=*win*" + monitoring/filter/objects = "host=*win*" -Will display only hosts and services whose hosts contains the **win**. +Will display only hosts and services whose host name contains **win**. - [nowin] + [webadmin] + groups = "web-admins" + monitoring/filter/objects = "host!=*win*" + +Will only match hosts and services whose host name does **not** contain **win** + +Notice that because of the behavior of two stacking filters, a user that is member of **windows-admins** and **web-admins**, will now be able to see both, Windows and non-Windows hosts and services. + +#### Example 2: Hostgroups + + [unix-server] groups = "unix-admins" - monitoring/hosts/filter = "host!=*win*" - monitoring/services/filter = "host!=*win*" + monitoring/filter/objects = "(hostgroup_name=bsd-servers|hostgroup_name=linux-servers)" -Will only match hosts and services whose host does **not** contain **win** - -Also notice that if a user that is member of both groups, **windows-admins** and **unix-admins**, -he wont be able to see any hosts, as both filters will be applied and remove any object from the query. - - -###### Example 2: Hostgroups - - [only-unix] - groups = "unix-admins" - monitoring/hosts/filter = "(hostgroup_name=bsd-servers|hostgroup_name=linux-servers)" - monitoring/services/filter = "(hostgroup_name=bsd-servers|hostgroup_name=linux-servers)" - - -This role allows all members of the group unix-admins to only see hosts and services +This role allows all members of the group unix-admins to see hosts and services that are part of the host-group linux-servers or the host-group bsd-servers. -## Modules - -When creating IW2 modules, it might be necessary to define new actions that can be granted or revoked to certain users. One example for this, is the monitoring module, that is the heart of the monitoring functionality of IW2.while - ## Troubleshooting Q: I cannot see the menu for changing roles in Icinga Web 2 diff --git a/modules/doc/doc/security.md b/modules/doc/doc/security.md new file mode 100644 index 000000000..b6bce4389 --- /dev/null +++ b/modules/doc/doc/security.md @@ -0,0 +1,65 @@ +# Security + +The monitoring module provides an additional set of restrictions and permissions +that can be used for access control. The following sections will list those +restrictions and permissions in detail: + + +## Permissions + +The Icinga Web 2 monitoring plugin can send commands to the current Icinga2 instance +through the command pipe. A user needs specific permissions to be able to send those +commands when using the monitoring plugin. + + +| Name | Permits | +|---------------------------------------------|-----------------------------------------------------------------------------| +| monitoring/command/* | Allow all commands | +| monitoring/command/schedule-check | Allow scheduling host and service checks' | +| monitoring/command/acknowledge-problem | Allow acknowledging host and service problems | +| monitoring/command/remove-acknowledgement | Allow removing problem acknowledgements | +| monitoring/command/comment/* | Allow adding and deleting host and service comments | +| monitoring/command/comment/add | Allow commenting on hosts and services | +| monitoring/command/downtime/delete | Allow deleting host and service downtimes' | +| monitoring/command/process-check-result | Allow processing host and service check results | +| monitoring/command/feature/instance | Allow processing commands for toggling features on an instance-wide basis | +| monitoring/command/feature/object | Allow processing commands for toggling features on host and service objects | +| monitoring/command/send-custom-notification | Allow sending custom notifications for hosts and services | + + +## Restrictions + +The monitoring module allows filtering objects: + + +| Keys | Restricts | +|----------------------------|-----------------------------------------------| +| monitoring/filter/objects | Applies a filter to all hosts and services | + + +This filter will affect all hosts and services. Furthermore, it will also +affect all related objects, like notifications, downtimes or events. If a +service is hidden, all notifications, downtimes on that service will be hidden too. + + +### Filter Column Names + +The following filter column names are available in filter expressions: + + +| Column | +|------------------------------------------------------| +| host | +| host_alias | +| host_display_name | +| host_name | +| hostgroup | +| hostgroup_alias | +| hostgroup_name | +| service | +| service_description | +| service_display_name | +| service_group | +| service_group_alias | +| service_group_name | +| + all custom variables prefixed with host or service | From 4a591db4381f6e128cebebd1eae3d0860d9205f3 Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Wed, 10 Jun 2015 19:01:33 +0200 Subject: [PATCH 102/116] Fix a few typos refs #9377 --- doc/security.md | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/doc/security.md b/doc/security.md index 1b61f8ab3..da1162496 100644 --- a/doc/security.md +++ b/doc/security.md @@ -29,7 +29,7 @@ section [Permissions](#permissions). There are all kinds of different objects in Icinga Web 2: Hosts, Services, Notifications, Downtimes and Events. -By default, a user can **see everything**, but it is possible to **explicitly restrict** what each user can see using Restrictions. +By default, a user can **see everything**, but it is possible to **explicitly restrict** what each user can see using restrictions. Restrictions are complex filter queries that describe what objects should be displayed to a user. Restrictions are described in greater detail in the section [Restrictions](#restrictions). @@ -42,7 +42,7 @@ For example, there might be user called **jdoe** authenticated using Active Directory, and a user **icingaadmin** that is authenticated using a MySQL-Database as backend. Both can be referenced by using their the user names **icingaadmin** or **jdoe**, when referring to those users in the configuration. Icinga Web 2 users and groups are not configured by a configuration file, but provided by -an **authentication backend**. For extended information on setting up authentication backends and managing users, please read the chapter [authentication](authentication.md#authentication). +an **authentication backend**. For extended information on setting up authentication backends and managing users, please read the chapter [Authentication](authentication.md#authentication).
    @@ -59,12 +59,13 @@ can be found at **Configuration > Authentication > Users **. ### Groups -If there is a big amounts of users to manage, it would be tedious to specify each user -separately when referring to a bigger group of users at once. Because of that, any user can be a member of multiple groups, and will inherit all permissions and restrictions. +If there is a big amount of users to manage, it would be tedious to specify each user +separately when referring to a bigger group of users often. Because of that, it is possible to group users together into groups. +A user can be member of multiple groups and will inherit all permissions and restrictions. Like users, groups are identified solely by their **name** that is provided by a **group backend**. For extended information on setting up group backends, - please read the chapter [authentication](authentication.md#authentication). + please read the chapter [Authentication](authentication.md#authentication). #### Managing Groups @@ -140,10 +141,10 @@ are in the namespace **config/authentication** **Wildcards** can be used to grant permission for all actions in a certain namespace. The permission **config/ ** would grant permission to all configuration actions, -while just specifying a wildcard ** * ** would give permission for all actions. +while just specifying a wildcard * would give permission for all actions. -When multiple roles assign permissions to the same user, either directly or indirectly -through a group, all permissions will be combined to get the users actual permission set. +When multiple roles assign permissions to the same user (either directly or indirectly +through a group) all permissions can simply be added together to get the users actual permission set. #### Global permissions @@ -187,7 +188,7 @@ are described in detail in [monitoring module documentation](/icingaweb2/doc/mod Restrictions can be used to define what a user or group can see by specifying a filter expression. By default, when no restrictions are defined, a user will be able to see every object available. When a restriction is specified for a certain object type -the user will only be able to see objects applied. +the user will only be able to see objects matched by those filters. ### Filterable Objects @@ -200,12 +201,14 @@ are the objects provided by the monitoring module. The monitoring module provide **hosts** and **services** as filterable objects: -| Keys | Restricts | +| Keys | Restricsts | |----------------------------|-----------------------------------------------| | monitoring/filter/objects | Applies host and service filters to all hosts | -A complete list of all available filter columns can be found in +#### Filter Columns + +Filters operate on object attributes. A complete list of all available filter columns on hosts and services can be found in the [monitoring module documentation](/icingaweb2/doc/module/doc/chapter/monitoring-security#monitoring-security-restrictions). ### Filter Expressions @@ -287,4 +290,4 @@ Q: I cannot see the menu for changing roles in Icinga Web 2 A: Your user lacks the permission to change the authentication configuration, add a new role in roles.ini that gives your current user - the permission to access **config/authentication/* ** + the permission to access **config/authentication/** From 6e28a58739908ee85d3931f42372092abe79cb24 Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Wed, 10 Jun 2015 19:06:59 +0200 Subject: [PATCH 103/116] Fix drawing in doc refs #9377 --- doc/security.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/security.md b/doc/security.md index da1162496..201a6533c 100644 --- a/doc/security.md +++ b/doc/security.md @@ -245,11 +245,11 @@ through a group, all filters will be combined using an **OR-Clause**, resulting expression: - AND -- OR-- $FILTER1 - | | - | +-- $FILTER2 - | | - | +-- $FILTER3 + AND-- OR-- $FILTER1 + | | + | +-- $FILTER2 + | | + | +-- $FILTER3 | +--AND-- service_problem = 1 | From 5b18ff7965529ecaa4bdd2eb2ab3796b30da771c Mon Sep 17 00:00:00 2001 From: Paul Richards Date: Mon, 30 Mar 2015 21:42:29 +0100 Subject: [PATCH 104/116] Fix PHP warning on windows due to LC_MESSAGES not defined. fixes #8912 --- AUTHORS | 1 + library/Icinga/Util/Translator.php | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/AUTHORS b/AUTHORS index 8a0bc91d0..f56d6e24d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -18,6 +18,7 @@ Marius Hein Markus Frosch Matthias Jentsch Michael Friedrich +Paul Richards Rene Moser Susanne Vestner-Ludwig Sylph Lin diff --git a/library/Icinga/Util/Translator.php b/library/Icinga/Util/Translator.php index 67825b63b..a4524cee7 100644 --- a/library/Icinga/Util/Translator.php +++ b/library/Icinga/Util/Translator.php @@ -5,6 +5,10 @@ namespace Icinga\Util; use Icinga\Exception\IcingaException; +if (!defined('LC_MESSAGES')) { + define('LC_MESSAGES', 6); +} + /** * Helper class to ease internationalization when using gettext */ From 4607a85cf5745bb99a12f4b5e829447d82c09f6e Mon Sep 17 00:00:00 2001 From: Paul Richards Date: Mon, 30 Mar 2015 23:09:24 +0100 Subject: [PATCH 105/116] IIS: use 'config' folder within icingaweb root on windows fixes #8916 --- library/Icinga/Application/ApplicationBootstrap.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Application/ApplicationBootstrap.php b/library/Icinga/Application/ApplicationBootstrap.php index e265b91ee..ce5997c50 100644 --- a/library/Icinga/Application/ApplicationBootstrap.php +++ b/library/Icinga/Application/ApplicationBootstrap.php @@ -138,7 +138,13 @@ abstract class ApplicationBootstrap if (array_key_exists('ICINGAWEB_CONFIGDIR', $_SERVER)) { $configDir = $_SERVER['ICINGAWEB_CONFIGDIR']; } else { - $configDir = '/etc/icingaweb2'; + // default configuration path. Use /etc/icingaweb2 on linux + // For config on windows - use config folder within php code + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + $configDir = $baseDir . '/config'; + } else { + $configDir = '/etc/icingaweb2'; + } } } $canonical = realpath($configDir); From 1e0219f6975eb6f47870c74a0edd374b9e51c7eb Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 12 Jun 2015 10:38:11 +0200 Subject: [PATCH 106/116] Revert "IIS: use 'config' folder within icingaweb root on windows" This reverts commit 4607a85cf5745bb99a12f4b5e829447d82c09f6e. --- library/Icinga/Application/ApplicationBootstrap.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/library/Icinga/Application/ApplicationBootstrap.php b/library/Icinga/Application/ApplicationBootstrap.php index ce5997c50..e265b91ee 100644 --- a/library/Icinga/Application/ApplicationBootstrap.php +++ b/library/Icinga/Application/ApplicationBootstrap.php @@ -138,13 +138,7 @@ abstract class ApplicationBootstrap if (array_key_exists('ICINGAWEB_CONFIGDIR', $_SERVER)) { $configDir = $_SERVER['ICINGAWEB_CONFIGDIR']; } else { - // default configuration path. Use /etc/icingaweb2 on linux - // For config on windows - use config folder within php code - if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { - $configDir = $baseDir . '/config'; - } else { - $configDir = '/etc/icingaweb2'; - } + $configDir = '/etc/icingaweb2'; } } $canonical = realpath($configDir); From 0bce5e83add6a830108bf95299e752a5604c5517 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 12 Jun 2015 10:38:25 +0200 Subject: [PATCH 107/116] Revert "Fix PHP warning on windows due to LC_MESSAGES not defined." This reverts commit 5b18ff7965529ecaa4bdd2eb2ab3796b30da771c. --- AUTHORS | 1 - library/Icinga/Util/Translator.php | 4 ---- 2 files changed, 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index f56d6e24d..8a0bc91d0 100644 --- a/AUTHORS +++ b/AUTHORS @@ -18,7 +18,6 @@ Marius Hein Markus Frosch Matthias Jentsch Michael Friedrich -Paul Richards Rene Moser Susanne Vestner-Ludwig Sylph Lin diff --git a/library/Icinga/Util/Translator.php b/library/Icinga/Util/Translator.php index a4524cee7..67825b63b 100644 --- a/library/Icinga/Util/Translator.php +++ b/library/Icinga/Util/Translator.php @@ -5,10 +5,6 @@ namespace Icinga\Util; use Icinga\Exception\IcingaException; -if (!defined('LC_MESSAGES')) { - define('LC_MESSAGES', 6); -} - /** * Helper class to ease internationalization when using gettext */ From 48870bb7e2ed392a6898591b20eace0c96f54993 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 12 Jun 2015 11:09:28 +0200 Subject: [PATCH 108/116] Don't use /etc/icingaweb2 as config path on Windows fixes #8916 --- library/Icinga/Application/ApplicationBootstrap.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/library/Icinga/Application/ApplicationBootstrap.php b/library/Icinga/Application/ApplicationBootstrap.php index e265b91ee..53c2c70c7 100644 --- a/library/Icinga/Application/ApplicationBootstrap.php +++ b/library/Icinga/Application/ApplicationBootstrap.php @@ -134,18 +134,20 @@ abstract class ApplicationBootstrap $this->vendorDir = $baseDir . '/library/vendor'; $this->libDir = realpath(__DIR__ . '/../..'); + $this->setupAutoloader(); + if ($configDir === null) { if (array_key_exists('ICINGAWEB_CONFIGDIR', $_SERVER)) { $configDir = $_SERVER['ICINGAWEB_CONFIGDIR']; } else { - $configDir = '/etc/icingaweb2'; + $configDir = Platform::isWindows() + ? $baseDir . DIRECTORY_SEPARATOR . 'config' + : '/etc/icingaweb2'; } } $canonical = realpath($configDir); $this->configDir = $canonical ? $canonical : $configDir; - $this->setupAutoloader(); - set_include_path( implode( PATH_SEPARATOR, From 9fe2d4928d2542348a0e6932951628588875396c Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 12 Jun 2015 12:28:30 +0200 Subject: [PATCH 109/116] Translator: fall back to LC_ALL if LC_MESSAGES is not defined fixes #8912 --- library/Icinga/Util/Translator.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/library/Icinga/Util/Translator.php b/library/Icinga/Util/Translator.php index 67825b63b..03fd865cd 100644 --- a/library/Icinga/Util/Translator.php +++ b/library/Icinga/Util/Translator.php @@ -100,7 +100,11 @@ class Translator { $contextString = "{$context}\004{$text}"; - $translation = dcgettext($domain, $contextString, LC_MESSAGES); + $translation = dcgettext( + $domain, + $contextString, + defined('LC_MESSAGES') ? LC_MESSAGES : LC_ALL + ); if ($translation == $contextString) { return $text; @@ -126,7 +130,13 @@ class Translator { $contextString = "{$context}\004{$textSingular}"; - $translation = dcngettext($domain, $contextString, $textPlural, $number, LC_MESSAGES); + $translation = dcngettext( + $domain, + $contextString, + $textPlural, + $number, + defined('LC_MESSAGES') ? LC_MESSAGES : LC_ALL + ); if ($translation == $contextString || $translation == $textPlural) { return ($number == 1 ? $textSingular : $textPlural); From b58ffbe0341291fb19d351e8c6586ca1df9c24f9 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 12 Jun 2015 13:29:31 +0200 Subject: [PATCH 110/116] Revert "Translator: fall back to LC_ALL if LC_MESSAGES is not defined" This reverts commit 9fe2d4928d2542348a0e6932951628588875396c. --- library/Icinga/Util/Translator.php | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/library/Icinga/Util/Translator.php b/library/Icinga/Util/Translator.php index 03fd865cd..67825b63b 100644 --- a/library/Icinga/Util/Translator.php +++ b/library/Icinga/Util/Translator.php @@ -100,11 +100,7 @@ class Translator { $contextString = "{$context}\004{$text}"; - $translation = dcgettext( - $domain, - $contextString, - defined('LC_MESSAGES') ? LC_MESSAGES : LC_ALL - ); + $translation = dcgettext($domain, $contextString, LC_MESSAGES); if ($translation == $contextString) { return $text; @@ -130,13 +126,7 @@ class Translator { $contextString = "{$context}\004{$textSingular}"; - $translation = dcngettext( - $domain, - $contextString, - $textPlural, - $number, - defined('LC_MESSAGES') ? LC_MESSAGES : LC_ALL - ); + $translation = dcngettext($domain, $contextString, $textPlural, $number, LC_MESSAGES); if ($translation == $contextString || $translation == $textPlural) { return ($number == 1 ? $textSingular : $textPlural); From 7ea6eeb20de335fa6f035405356f1d82795be66b Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 12 Jun 2015 13:29:36 +0200 Subject: [PATCH 111/116] Revert "Don't use /etc/icingaweb2 as config path on Windows" This reverts commit 48870bb7e2ed392a6898591b20eace0c96f54993. --- library/Icinga/Application/ApplicationBootstrap.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/library/Icinga/Application/ApplicationBootstrap.php b/library/Icinga/Application/ApplicationBootstrap.php index 53c2c70c7..e265b91ee 100644 --- a/library/Icinga/Application/ApplicationBootstrap.php +++ b/library/Icinga/Application/ApplicationBootstrap.php @@ -134,20 +134,18 @@ abstract class ApplicationBootstrap $this->vendorDir = $baseDir . '/library/vendor'; $this->libDir = realpath(__DIR__ . '/../..'); - $this->setupAutoloader(); - if ($configDir === null) { if (array_key_exists('ICINGAWEB_CONFIGDIR', $_SERVER)) { $configDir = $_SERVER['ICINGAWEB_CONFIGDIR']; } else { - $configDir = Platform::isWindows() - ? $baseDir . DIRECTORY_SEPARATOR . 'config' - : '/etc/icingaweb2'; + $configDir = '/etc/icingaweb2'; } } $canonical = realpath($configDir); $this->configDir = $canonical ? $canonical : $configDir; + $this->setupAutoloader(); + set_include_path( implode( PATH_SEPARATOR, From e8cdcce61df3c47f5ba55621bd14e8aac8f5af5d Mon Sep 17 00:00:00 2001 From: Paul Richards Date: Mon, 30 Mar 2015 21:42:29 +0100 Subject: [PATCH 112/116] Fix PHP warning on Windows due to LC_MESSAGES not defined Signed-off-by: Alexander A. Klimov with the following changes: Don't define LC_MESSAGES globally as only 2 methods would need that Use LC_ALL rather than 6 fixes #8912 --- AUTHORS | 1 + library/Icinga/Util/Translator.php | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 8a0bc91d0..f56d6e24d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -18,6 +18,7 @@ Marius Hein Markus Frosch Matthias Jentsch Michael Friedrich +Paul Richards Rene Moser Susanne Vestner-Ludwig Sylph Lin diff --git a/library/Icinga/Util/Translator.php b/library/Icinga/Util/Translator.php index 67825b63b..03fd865cd 100644 --- a/library/Icinga/Util/Translator.php +++ b/library/Icinga/Util/Translator.php @@ -100,7 +100,11 @@ class Translator { $contextString = "{$context}\004{$text}"; - $translation = dcgettext($domain, $contextString, LC_MESSAGES); + $translation = dcgettext( + $domain, + $contextString, + defined('LC_MESSAGES') ? LC_MESSAGES : LC_ALL + ); if ($translation == $contextString) { return $text; @@ -126,7 +130,13 @@ class Translator { $contextString = "{$context}\004{$textSingular}"; - $translation = dcngettext($domain, $contextString, $textPlural, $number, LC_MESSAGES); + $translation = dcngettext( + $domain, + $contextString, + $textPlural, + $number, + defined('LC_MESSAGES') ? LC_MESSAGES : LC_ALL + ); if ($translation == $contextString || $translation == $textPlural) { return ($number == 1 ? $textSingular : $textPlural); From edd8f5738fb66dd9c1a46eef7c162d92dcdea0b1 Mon Sep 17 00:00:00 2001 From: Paul Richards Date: Mon, 30 Mar 2015 22:22:21 +0100 Subject: [PATCH 113/116] Windows: Fix webrouter on IIS Signed-off-by: Alexander A. Klimov with the following changes: Remove unneeded whitespace fixes #8914 --- library/Icinga/Application/webrouter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Application/webrouter.php b/library/Icinga/Application/webrouter.php index 905fa7dca..5a7d1c95c 100644 --- a/library/Icinga/Application/webrouter.php +++ b/library/Icinga/Application/webrouter.php @@ -31,7 +31,7 @@ $baseDir = $_SERVER['DOCUMENT_ROOT']; $baseDir = dirname($_SERVER['SCRIPT_FILENAME']); // Fix aliases -$remove = dirname($_SERVER['PHP_SELF']); +$remove = str_replace('\\', '/', dirname($_SERVER['PHP_SELF'])); if (substr($ruri, 0, strlen($remove)) !== $remove) { return false; } From aac5d49eed69a661653a604c4f2671da3745e31d Mon Sep 17 00:00:00 2001 From: Paul Richards Date: Mon, 30 Mar 2015 23:09:24 +0100 Subject: [PATCH 114/116] IIS: use 'config' folder within icingaweb root instead of '/etc/icingaweb2' on Windows Signed-off-by: Alexander A. Klimov with the following changes: Make use of Platform::isWindows() Call ::setupAutoloader() before that fixes #8916 --- library/Icinga/Application/ApplicationBootstrap.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/library/Icinga/Application/ApplicationBootstrap.php b/library/Icinga/Application/ApplicationBootstrap.php index e265b91ee..6f409789a 100644 --- a/library/Icinga/Application/ApplicationBootstrap.php +++ b/library/Icinga/Application/ApplicationBootstrap.php @@ -134,18 +134,20 @@ abstract class ApplicationBootstrap $this->vendorDir = $baseDir . '/library/vendor'; $this->libDir = realpath(__DIR__ . '/../..'); + $this->setupAutoloader(); + if ($configDir === null) { if (array_key_exists('ICINGAWEB_CONFIGDIR', $_SERVER)) { $configDir = $_SERVER['ICINGAWEB_CONFIGDIR']; } else { - $configDir = '/etc/icingaweb2'; + $configDir = Platform::isWindows() + ? $baseDir . '/config' + : '/etc/icingaweb2'; } } $canonical = realpath($configDir); $this->configDir = $canonical ? $canonical : $configDir; - $this->setupAutoloader(); - set_include_path( implode( PATH_SEPARATOR, From ea85254cc5b243d8ed6bce3ab6e15221cc39aeb9 Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Mon, 15 Jun 2015 11:09:41 +0200 Subject: [PATCH 115/116] Fix typos and formatting in documentation refs #9377 --- doc/security.md | 158 ++++++++++++++++-------------------- modules/doc/doc/security.md | 4 +- 2 files changed, 74 insertions(+), 88 deletions(-) diff --git a/doc/security.md b/doc/security.md index 201a6533c..f975c5c9b 100644 --- a/doc/security.md +++ b/doc/security.md @@ -2,24 +2,28 @@ Access control is a vital part of configuring Icinga Web 2 in a secure way. It is important that not every user that has access to Icinga Web 2 is able -to do any action or see any host and service. For example, it is useful to allow -only a small group of administrators to change the Icinga Web 2 configuration, to prevent misconfiguration or security breaches. Another important use case is the creation groups of -users that can only see the fraction of hosts and services of the monitoring - environment they are in charge of. +to do any action or to see any host and service. For example, it is useful to allow +only a small group of administrators to change the Icinga Web 2 configuration, +to prevent misconfiguration or security breaches. Another important use case is +creating groups of users which can only see the fraction of the monitoring +environment they are in charge of. This chapter will describe how to do the security configuration of Icinga Web 2 -and how apply permissions and restrictions to users or groups of users. +and how to apply permissions and restrictions to users or groups of users. ## Basics -Icinga Web 2 access control is done by defining **roles** that relate permissions or restrictions to **users** and **group**. -There are two general kinds of things to which access can be managed: actions and objects. +Icinga Web 2 access control is done by defining **roles** that associate permissions +and restrictions with **users** and **groups**. There are two general kinds of +things to which access can be managed: actions and objects. ### Actions Actions are all the things an Icinga Web 2 user can do, like changing a certain configuration, -changing permissions or sending a command to the Icinga instance through the Command Pipe in the monitoring module. All actions must be be **allowed explicitly** using permissions. +changing permissions or sending a command to the Icinga instance through the +Command Pipe +in the monitoring module. All actions must be be **allowed explicitly** using permissions. A permission is a simple list of identifiers of actions a user is allowed to do. Permissions are described in greater detail in the @@ -39,7 +43,8 @@ in greater detail in the section [Restrictions](#restrictions). Anyone who can **login** to Icinga Web 2 is considered a user and can be referenced to by the **user name** used during login. For example, there might be user called **jdoe** authenticated -using Active Directory, and a user **icingaadmin** that is authenticated using a MySQL-Database as backend. Both can be referenced by using their the user names **icingaadmin** or **jdoe**, when referring to those users in the configuration. +using Active Directory, and a user **icingaadmin** that is authenticated using a MySQL-Database as backend. +In the configuration, both can be referenced to by using their user names **icingaadmin** or **jdoe**. Icinga Web 2 users and groups are not configured by a configuration file, but provided by an **authentication backend**. For extended information on setting up authentication backends and managing users, please read the chapter [Authentication](authentication.md#authentication). @@ -60,7 +65,7 @@ can be found at **Configuration > Authentication > Users **. ### Groups If there is a big amount of users to manage, it would be tedious to specify each user -separately when referring to a bigger group of users often. Because of that, it is possible to group users together into groups. +separately when regularly referring to the same group of users. Because of that, it is possible to group users. A user can be member of multiple groups and will inherit all permissions and restrictions. Like users, groups are identified solely by their **name** that is provided by @@ -71,7 +76,7 @@ Like users, groups are identified solely by their **name** that is provided by #### Managing Groups When using a [Database as an authentication backend](#authentication.md#authentication-configuration-db-authentication), -it is possible manage groups and group memberships directly in the frontend. This configuration +it is possible to manage groups and group memberships directly in the frontend. This configuration can be found at **Configuration > Authentication > Groups **. ## Roles @@ -82,7 +87,8 @@ users have access to all configuration options, or another role **support** could define that a list of users or groups is restricted to see only hosts and services that match a specific query. -The actual permission of a certain user will be determined by merging the permissions and restrictions of the user itself and all the groups the user is part of. Permissions can +The actual permission of a certain user will be determined by merging the permissions +and restrictions of the user itself and all the groups the user is member of. Permissions can be simply added up, while restrictions follow a slighty more complex pattern, that is described in the section [Stacking Filters](#stacking-filters). @@ -98,7 +104,7 @@ configuration file: #### Introducing Example -To get you a quick start, here is an example of how a role definition could look like: +To get you a quick start, here is an example of what a role definition could look like: [winadmin] @@ -108,24 +114,24 @@ To get you a quick start, here is an example of how a role definition could look monitoring/filter/objects = "host=*win*" -This example creates a role called **winadmin**, that grants all permissions in **config/application/** and ** monitoring/commands/schedule-check ** and additionally only -allows the hosts and services that match the filter ** host=\*win* ** to be displayed. The users +This example creates a role called **winadmin**, that grants all permissions in `config/application/*` and `monitoring/commands/schedule-check` and additionally only +allows the hosts and services that match the filter `host=*win*` to be displayed. The users **jdoe** and **janedoe** and all members of the group **admin** will be affected by this role. -#### Syntax +#### Syntax Each role is defined as a section, with the name of the role as section name. The following attributes can be defined for each role in a default Icinga Web 2 installation: -| Key | Value | -|---------------------------|---------------------------------------------------------------------------------| -| users | A comma-separated list of user **user names** that are affected by this role | -| groups | A comma-separated list of **group names** that are affected by this role | -| permissions | A comma-separated list of **permissions** granted by this role | -| monitoring/filter/objects | A **filter expression** that restricts the access to services and hosts | + Directive | Description +---------------------------|----------------------------------------------------------------------------- + users | A comma-separated list of user **user names** that are affected by this role + groups | A comma-separated list of **group names** that are affected by this role + permissions | A comma-separated list of **permissions** granted by this role + monitoring/filter/objects | A **filter expression** that restricts the access to services and hosts @@ -137,87 +143,75 @@ all actions are **prohibited** and must be allowed explicitly by a role for any Each action in Icinga Web 2 is denoted by a **namespaced key**, which is used to order and group those actions. All actions that affect the configuration of Icinga Web 2, are in a namespace called **config**, while all configurations that affect authentication -are in the namespace **config/authentication** +are in the namespace `config/authentication` **Wildcards** can be used to grant permission for all actions in a certain namespace. -The permission **config/ ** would grant permission to all configuration actions, -while just specifying a wildcard * would give permission for all actions. +The permission `config/*` would grant permission to all configuration actions, +while just specifying a wildcard `*` would give permission for all actions. When multiple roles assign permissions to the same user (either directly or indirectly through a group) all permissions can simply be added together to get the users actual permission set. #### Global permissions -| Name | Permits | -|-------------------------------------|-----------------------------------------------------------------| -| * | Allow everything, including module-specific permissions | -| config/* | Allow all configuration actions | -| config/application/* | Allow configuring IcingaWeb2 | -| config/application/general | Allow general settings, like logging or preferences | -| config/application/resources | Allow changing resources for retrieving data | -| config/application/userbackend | Allow changing backends for retrieving available users | -| config/application/usergroupbackend | Allow changing backends for retrieving available groups | -| config/authentication/* | Allow configuring IcingaWeb2 authentication mechanisms | -| config/authentication/users/* | Allow all user actions | -| config/authentication/users/show | Allow displaying avilable users | -| config/authentication/users/add | Allow adding a new user to the backend | -| config/authentication/users/edit | Allow editing an existing user in the backend | -| config/authentication/users/remove | Allow removing an existing user from the backend | -| config/authentication/groups/* | Allow all group actions | -| config/authentication/groups/show | Allow displaying all available groups | -| config/authentication/groups/add | Allow adding a new group to the backend | -| config/authentication/groups/edit | Allow editing existing groups in a backend | -| config/authentication/groups/remove | Allow removing existing groups from the backend | -| config/authentication/roles/* | Allow all role actions | -| config/authentication/roles/add | Allow adding a new role | -| config/authentication/roles/show | Allow displaying available roles | -| config/authentication/roles/edit | Allow changing an existing role | -| config/authentication/roles/remove | Allow removing an existing row | -| config/modules | Allow enabling or disable modules and module changing settings | + Name | Permits +-------------------------------------|----------------------------------------------------------------- + * | Allow everything, including module-specific permissions + config/* | Allow all configuration actions + config/application/* | Allow configuring IcingaWeb2 + config/application/general | Allow general settings, like logging or preferences + config/application/resources | Allow changing resources for retrieving data + config/application/userbackend | Allow changing backends for retrieving available users + config/application/usergroupbackend | Allow changing backends for retrieving available groups + config/authentication/* | Allow configuring IcingaWeb2 authentication mechanisms + config/authentication/users/* | Allow all user actions + config/authentication/users/show | Allow displaying avilable users + config/authentication/users/add | Allow adding a new user to the backend + config/authentication/users/edit | Allow editing an existing user in the backend + config/authentication/users/remove | Allow removing an existing user from the backend + config/authentication/groups/* | Allow all group actions + config/authentication/groups/show | Allow displaying all available groups + config/authentication/groups/add | Allow adding a new group to the backend + config/authentication/groups/edit | Allow editing existing groups in a backend + config/authentication/groups/remove | Allow removing existing groups from the backend + config/authentication/roles/* | Allow all role actions + config/authentication/roles/add | Allow adding a new role + config/authentication/roles/show | Allow displaying available roles + config/authentication/roles/edit | Allow changing an existing role + config/authentication/roles/remove | Allow removing an existing row + config/modules | Allow enabling or disabling modules #### Monitoring module permissions The built-in monitoring module defines an additional set of permissions, that -are described in detail in [monitoring module documentation](/icingaweb2/doc/module/doc/chapter/monitoring-security#monitoring-security). - +is described in detail in [monitoring module documentation](/icingaweb2/doc/module/doc/chapter/monitoring-security#monitoring-security). ## Restrictions Restrictions can be used to define what a user or group can see by specifying -a filter expression. By default, when no restrictions are defined, a user will be able -to see every object available. When a restriction is specified for a certain object type -the user will only be able to see objects matched by those filters. +a filter expression that applies to a defined set of data. By default, when no +restrictions are defined, a user will be able to see every information that is available. - -### Filterable Objects - -Each filter expression consists of two parts: The **key** that defines **which** objects -to filter and the filter, that defines **what** to filter. - -In a default Icinga Web 2 installations, the only objects that are filterable -are the objects provided by the monitoring module. The monitoring module provides -**hosts** and **services** as filterable objects: - - -| Keys | Restricsts | -|----------------------------|-----------------------------------------------| -| monitoring/filter/objects | Applies host and service filters to all hosts | - - -#### Filter Columns - -Filters operate on object attributes. A complete list of all available filter columns on hosts and services can be found in -the [monitoring module documentation](/icingaweb2/doc/module/doc/chapter/monitoring-security#monitoring-security-restrictions). +A restrictions is always specified for a certain **filter directive**, that defines what +data the filter is applied to. The **filter directive** is a simple identifier, that was +defined in an Icinga Web 2 module. The only filter directive that is available +in a default installation, is the `monitoring/filter/objects` directive, defined by the monitoring module, +that can be used to apply filter to hosts and services. This directive was previously +mentioned in the section [Syntax](#syntax). ### Filter Expressions +Filters operate on columns. A complete list of all available filter columns on hosts and services can be found in +the [monitoring module documentation](/icingaweb2/doc/module/doc/chapter/monitoring-security#monitoring-security-restrictions). + Any filter expression that is allowed in the filtered view, is also an allowed filter expression. This means, that it is possible to define negations, wildcards, and even nested filter expressions containing AND and OR-Clauses. -The filter expression will be **implicitly** added as an **AND-Clause** to each query on the filtered objects. The following shows the filter expression **host = \*win* ** being applied on ** monitoring/filter/objects **. +The filter expression will be **implicitly** added as an **AND-Clause** to each query on +the filtered data. The following shows the filter expression `host=*win*` being applied on `monitoring/filter/objects`. Regular filter query: @@ -282,12 +276,4 @@ Notice that because of the behavior of two stacking filters, a user that is memb monitoring/filter/objects = "(hostgroup_name=bsd-servers|hostgroup_name=linux-servers)" This role allows all members of the group unix-admins to see hosts and services -that are part of the host-group linux-servers or the host-group bsd-servers. - -## Troubleshooting - -Q: I cannot see the menu for changing roles in Icinga Web 2 - -A: Your user lacks the permission to change the authentication configuration, - add a new role in roles.ini that gives your current user - the permission to access **config/authentication/** +that are part of the host-group linux-servers or the host-group bsd-servers. \ No newline at end of file diff --git a/modules/doc/doc/security.md b/modules/doc/doc/security.md index b6bce4389..023a66410 100644 --- a/modules/doc/doc/security.md +++ b/modules/doc/doc/security.md @@ -7,9 +7,9 @@ restrictions and permissions in detail: ## Permissions -The Icinga Web 2 monitoring plugin can send commands to the current Icinga2 instance +The Icinga Web 2 monitoring module can send commands to the current Icinga2 instance through the command pipe. A user needs specific permissions to be able to send those -commands when using the monitoring plugin. +commands when using the monitoring module. | Name | Permits | From 1637f4760ffec0c43c6fa42f6d7ecaf0cdb7c8fb Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Mon, 15 Jun 2015 17:23:52 +0200 Subject: [PATCH 116/116] Fix table formatting refs #7595 --- doc/resources.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/resources.md b/doc/resources.md index 19cb84a57..a2bfb66af 100644 --- a/doc/resources.md +++ b/doc/resources.md @@ -73,7 +73,7 @@ Directive | Description --------------------|------------ **type** | `ssh` **user** | The username to use when connecting to the server. -**private_key** | The path to the private key of the user. +**private_key** | The path to the private key of the user. **Example:**