From bd424a7cbdcd4985435449b0106d8ec4d326d1c3 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Sat, 10 Oct 2015 20:53:09 +0200 Subject: [PATCH 01/84] iframe: Do not escape the url The url is already escaped. fixes #10321 --- modules/iframe/application/views/scripts/index/index.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/iframe/application/views/scripts/index/index.phtml b/modules/iframe/application/views/scripts/index/index.phtml index cd5184009..be15b5dcc 100644 --- a/modules/iframe/application/views/scripts/index/index.phtml +++ b/modules/iframe/application/views/scripts/index/index.phtml @@ -1 +1 @@ - + From eed195922349babc3e9f7512bf051825adc82b13 Mon Sep 17 00:00:00 2001 From: Marius Hein Date: Wed, 14 Oct 2015 12:31:46 +0200 Subject: [PATCH 02/84] Rename manifest file --- modules/iframe/{moduel.info => module.info} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/iframe/{moduel.info => module.info} (100%) diff --git a/modules/iframe/moduel.info b/modules/iframe/module.info similarity index 100% rename from modules/iframe/moduel.info rename to modules/iframe/module.info From 34f4eb932171871b31a48a686cf81593afd475b1 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Thu, 15 Oct 2015 11:01:04 +0200 Subject: [PATCH 03/84] Revert "iframe: Do not escape the url" This reverts commit bd424a7cbdcd4985435449b0106d8ec4d326d1c3. refs #10321 --- modules/iframe/application/views/scripts/index/index.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/iframe/application/views/scripts/index/index.phtml b/modules/iframe/application/views/scripts/index/index.phtml index be15b5dcc..cd5184009 100644 --- a/modules/iframe/application/views/scripts/index/index.phtml +++ b/modules/iframe/application/views/scripts/index/index.phtml @@ -1 +1 @@ - + From 81aac2f6aa0d7dc7637515b61e5588f29f28a998 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Thu, 15 Oct 2015 11:01:58 +0200 Subject: [PATCH 04/84] Revert "Rename manifest file" This reverts commit eed195922349babc3e9f7512bf051825adc82b13. There's a pull request on GitHub open for that one. --- modules/iframe/{module.info => moduel.info} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/iframe/{module.info => moduel.info} (100%) diff --git a/modules/iframe/module.info b/modules/iframe/moduel.info similarity index 100% rename from modules/iframe/module.info rename to modules/iframe/moduel.info From ac7546d9f2f166a3bebbbb9d5941b2084c1ce00b Mon Sep 17 00:00:00 2001 From: Vladislav Ponomarev Date: Fri, 2 Oct 2015 15:40:08 +0200 Subject: [PATCH 05/84] Fix group base DN is erroneously used in place of user base DN refs #10340 refs #10367 Signed-off-by: Eric Lippmann --- .../Icinga/Authentication/UserGroup/LdapUserGroupBackend.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index 2a737d535..f50371137 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -607,7 +607,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt return $this ->setGroupBaseDn($config->base_dn) - ->setUserBaseDn($config->get('user_base_dn', $this->getGroupBaseDn())) + ->setUserBaseDn($config->get('user_base_dn', $this->getUserBaseDn())) ->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)) From 21688393496467b75c568c0613e18c2d8439072d Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 15 Oct 2015 13:45:43 +0200 Subject: [PATCH 06/84] user/show.phtml: Fix coding style refs #10367 --- application/views/scripts/user/show.phtml | 132 +++++++++++----------- 1 file changed, 64 insertions(+), 68 deletions(-) diff --git a/application/views/scripts/user/show.phtml b/application/views/scripts/user/show.phtml index ee3aa6809..97566a782 100644 --- a/application/views/scripts/user/show.phtml +++ b/application/views/scripts/user/show.phtml @@ -24,35 +24,35 @@ if ($this->hasPermission('config/authentication/users/edit') && $backend instanc ?>
compact): ?> - + -

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'); ?>

+

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'); ?>

compact): ?> - sortBox; ?> + sortBox; ?> - limiter; ?> - paginator; ?> + limiter; ?> + paginator; ?> compact): ?> - filterEditor; ?> + filterEditor; ?>
- + qlink( $this->translate('Add User to Group') , 'user/createmembership', @@ -64,51 +64,47 @@ if ($this->hasPermission('config/authentication/users/edit') && $backend instanc 'title' => $this->translate('Add user to user group') ) ) ?> - - - - - - - - - - - - - - - - - - -hasResult()): ?> - -
translate('Group'); ?>translate('Cancel', 'group.membership'); ?>
- hasPermission('config/authentication/groups/show') && $membership->backend instanceof Selectable): ?> - qlink($membership->group_name, 'group/show', array( - 'backend' => $membership->backend->getName(), - 'group' => $membership->group_name - ), array( - 'title' => sprintf($this->translate('Show detailed information for group %s'), $membership->group_name) - )); ?> - - escape($membership->group_name); ?> - - - backend instanceof Reducible): ?> - setAction($this->url('group/removemember', array( - 'backend' => $membership->backend->getName(), - 'group' => $membership->group_name - ))); ?> - - - - -
- -

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

+ +hasResult()): ?> +

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

+ + + + + + + + + + + + + + + + + +
translate('Group'); ?>translate('Cancel', 'group.membership'); ?>
+ hasPermission('config/authentication/groups/show') && $membership->backend instanceof Selectable): ?> + qlink($membership->group_name, 'group/show', array( + 'backend' => $membership->backend->getName(), + 'group' => $membership->group_name + ), array( + 'title' => sprintf($this->translate('Show detailed information for group %s'), $membership->group_name) + )); ?> + + escape($membership->group_name); ?> + + + backend instanceof Reducible): ?> + setAction($this->url('group/removemember', array( + 'backend' => $membership->backend->getName(), + 'group' => $membership->group_name + ))); ?> + + - + +
+ \ No newline at end of file From f6e5fac23e677ff86f68bec2d1a9eeb2276d5ede Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 15 Oct 2015 13:46:34 +0200 Subject: [PATCH 07/84] user/show.phtml: Pass through a user's name when creating memberships refs #10367 --- application/views/scripts/user/show.phtml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/application/views/scripts/user/show.phtml b/application/views/scripts/user/show.phtml index 97566a782..6500e9ade 100644 --- a/application/views/scripts/user/show.phtml +++ b/application/views/scripts/user/show.phtml @@ -56,7 +56,10 @@ if ($this->hasPermission('config/authentication/users/edit') && $backend instanc qlink( $this->translate('Add User to Group') , 'user/createmembership', - array('backend' => $backend->getName()), + array( + 'backend' => $backend->getName(), + 'user' => $user->user_name + ), array( 'class' => 'button-link', 'data-base-target' => '_next', From 4d5fde768a872111fc6c8f660af093e65108e7b2 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 15 Oct 2015 14:02:14 +0200 Subject: [PATCH 08/84] user/show.phtml: Fix presentation and behaviour of the membership link * Removed base target _next, _self feels more natural * Removed obsolete link title, it has already a sufficient label * Restored previous link label, we're using the term membership everywhere else, so why should we make an exception here? refs #10367 --- application/views/scripts/user/show.phtml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/application/views/scripts/user/show.phtml b/application/views/scripts/user/show.phtml index 6500e9ade..5ad1e6865 100644 --- a/application/views/scripts/user/show.phtml +++ b/application/views/scripts/user/show.phtml @@ -54,17 +54,15 @@ if ($this->hasPermission('config/authentication/users/edit') && $backend instanc
qlink( - $this->translate('Add User to Group') , + $this->translate('Create New Membership') , 'user/createmembership', array( 'backend' => $backend->getName(), 'user' => $user->user_name ), array( - 'class' => 'button-link', - 'data-base-target' => '_next', - 'icon' => 'plus', - 'title' => $this->translate('Add user to user group') + 'icon' => 'plus', + 'class' => 'button-link' ) ) ?> From 635bb3eec666dc331f67faa4a0f0517f44aeb309 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 15 Oct 2015 14:35:30 +0200 Subject: [PATCH 09/84] user/show.phtml: Improve layout refs #10367 --- application/controllers/UserController.php | 2 +- application/views/scripts/user/show.phtml | 24 +++++++++---------- public/css/icinga/main-content.less | 27 ---------------------- 3 files changed, 13 insertions(+), 40 deletions(-) diff --git a/application/controllers/UserController.php b/application/controllers/UserController.php index d080c1dae..9b2845758 100644 --- a/application/controllers/UserController.php +++ b/application/controllers/UserController.php @@ -140,7 +140,7 @@ class UserController extends AuthBackendController $removeForm->addElement('button', 'btn_submit', array( 'escape' => false, 'type' => 'submit', - 'class' => 'link-like spinner', + 'class' => 'link-button spinner', 'value' => 'btn_submit', 'decorators' => array('ViewHelper'), 'label' => $this->view->icon('trash'), diff --git a/application/views/scripts/user/show.phtml b/application/views/scripts/user/show.phtml index 5ad1e6865..aa440ff42 100644 --- a/application/views/scripts/user/show.phtml +++ b/application/views/scripts/user/show.phtml @@ -22,7 +22,7 @@ if ($this->hasPermission('config/authentication/users/edit') && $backend instanc } ?> -
+
compact): ?> @@ -41,17 +41,17 @@ if ($this->hasPermission('config/authentication/users/edit') && $backend instanc last_modified === null ? '-' : $this->formatDateTime($user->last_modified); ?> +compact): ?>

translate('Group Memberships'); ?>

-compact): ?> - sortBox; ?> - - limiter; ?> - paginator; ?> -compact): ?> +
+ limiter; ?> + paginator; ?> + sortBox; ?> +
filterEditor; ?>
-
+
qlink( $this->translate('Create New Membership') , @@ -75,14 +75,14 @@ if ($this->hasPermission('config/authentication/users/edit') && $backend instanc - - + + - - -
translate('Group'); ?>translate('Cancel', 'group.membership'); ?>translate('Group'); ?>translate('Cancel', 'group.membership'); ?>
+ hasPermission('config/authentication/groups/show') && $membership->backend instanceof Selectable): ?> qlink($membership->group_name, 'group/show', array( 'backend' => $membership->backend->getName(), @@ -94,7 +94,7 @@ if ($this->hasPermission('config/authentication/users/edit') && $backend instanc escape($membership->group_name); ?> + backend instanceof Reducible): ?> setAction($this->url('group/removemember', array( 'backend' => $membership->backend->getName(), diff --git a/public/css/icinga/main-content.less b/public/css/icinga/main-content.less index 54430ef60..05c41ce62 100644 --- a/public/css/icinga/main-content.less +++ b/public/css/icinga/main-content.less @@ -233,33 +233,6 @@ div.content.users { } } -div.content.memberships { - table.membership-list { - th.membership-cancel { - width: 8em; - padding-right: 0.5em; - text-align: right; - } - - td.membership-cancel { - text-align: right; - - form button.link-like { - color: inherit; - } - } - } - - p { - margin-top: 0; - } - - a.membership-create { - display: block; - margin-top: 1em; - } -} - div.content.groups { table.group-list { th.group-remove { From 34bf0c3cb0c868f882fe8deac349acc24a82e151 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 15 Oct 2015 15:28:03 +0200 Subject: [PATCH 10/84] Add method getUserBackendName() to UserGroupBackendInterface refs #10367 refs #10373 --- application/views/scripts/group/show.phtml | 7 ++----- .../UserGroup/DbUserGroupBackend.php | 12 ++++++++++++ .../UserGroup/LdapUserGroupBackend.php | 15 +++++++++++++++ .../UserGroup/UserGroupBackendInterface.php | 9 +++++++++ 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/application/views/scripts/group/show.phtml b/application/views/scripts/group/show.phtml index f76ecdf3f..8d81683ee 100644 --- a/application/views/scripts/group/show.phtml +++ b/application/views/scripts/group/show.phtml @@ -2,7 +2,6 @@ use Icinga\Data\Extensible; use Icinga\Data\Updatable; -use Icinga\Data\Selectable; $extensible = $this->hasPermission('config/authentication/groups/add') && $backend instanceof Extensible; @@ -85,12 +84,10 @@ foreach ($members as $member): ?> hasPermission('config/authentication/users/show') - && method_exists($backend, 'getUserBackend') - && ($userBackend = $backend->getUserBackend()) !== null - && $userBackend instanceof Selectable + && ($userBackend = $backend->getUserBackendName($member->user_name)) !== null ): ?> qlink($member->user_name, 'user/show', array( - 'backend' => $userBackend->getName(), + 'backend' => $userBackend, 'user' => $member->user_name ), array( 'title' => sprintf($this->translate('Show detailed information about %s'), $member->user_name) diff --git a/library/Icinga/Authentication/UserGroup/DbUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/DbUserGroupBackend.php index a114cc8d1..5596f18b6 100644 --- a/library/Icinga/Authentication/UserGroup/DbUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/DbUserGroupBackend.php @@ -217,6 +217,18 @@ class DbUserGroupBackend extends DbRepository implements UserGroupBackendInterfa return $memberships; } + /** + * Return the name of the backend that is providing the given user + * + * @param string $username Currently unused + * + * @return null|string The name of the backend or null in case this information is not available + */ + public function getUserBackendName($username) + { + return null; // TODO(10373): Store this to the database when inserting and fetch it here + } + /** * Join group into group_membership * diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index 54ec1763b..1b1bb31d0 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -560,6 +560,21 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt return $groups; } + /** + * Return the name of the backend that is providing the given user + * + * @param string $username Unused + * + * @return null|string The name of the backend or null in case this information is not available + */ + public function getUserBackendName($username) + { + $userBackend = $this->getUserBackend(); + if ($userBackend !== null) { + return $userBackend->getName(); + } + } + /** * Apply the given configuration on this backend * diff --git a/library/Icinga/Authentication/UserGroup/UserGroupBackendInterface.php b/library/Icinga/Authentication/UserGroup/UserGroupBackendInterface.php index a567d1f0a..31dd2c50d 100644 --- a/library/Icinga/Authentication/UserGroup/UserGroupBackendInterface.php +++ b/library/Icinga/Authentication/UserGroup/UserGroupBackendInterface.php @@ -34,4 +34,13 @@ interface UserGroupBackendInterface * @return array */ public function getMemberships(User $user); + + /** + * Return the name of the backend that is providing the given user + * + * @param string $username + * + * @return null|string The name of the backend or null in case this information is not available + */ + public function getUserBackendName($username); } From 32955dea50844ab3b2eea6ab650f1afaa8fe4315 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 15 Oct 2015 15:46:04 +0200 Subject: [PATCH 11/84] user/show.phtml: Fix some minor issues refs #10367 --- application/views/scripts/user/show.phtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/views/scripts/user/show.phtml b/application/views/scripts/user/show.phtml index aa440ff42..2fbdab551 100644 --- a/application/views/scripts/user/show.phtml +++ b/application/views/scripts/user/show.phtml @@ -54,7 +54,7 @@ if ($this->hasPermission('config/authentication/users/edit') && $backend instanc
qlink( - $this->translate('Create New Membership') , + $this->translate('Create New Membership'), 'user/createmembership', array( 'backend' => $backend->getName(), @@ -94,7 +94,7 @@ if ($this->hasPermission('config/authentication/users/edit') && $backend instanc escape($membership->group_name); ?>
+ backend instanceof Reducible): ?> setAction($this->url('group/removemember', array( 'backend' => $membership->backend->getName(), From 0ee73f2560af61070672fe6f077dd4a1bb06d88f Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 15 Oct 2015 15:47:38 +0200 Subject: [PATCH 12/84] group/show.phtml: Fix coding style, link targets, css, etc refs #10367 --- application/controllers/GroupController.php | 2 +- application/views/scripts/group/show.phtml | 140 ++++++++++---------- public/css/icinga/main-content.less | 27 ---- 3 files changed, 69 insertions(+), 100 deletions(-) diff --git a/application/controllers/GroupController.php b/application/controllers/GroupController.php index 4dd05fdef..3f14b2090 100644 --- a/application/controllers/GroupController.php +++ b/application/controllers/GroupController.php @@ -134,7 +134,7 @@ class GroupController extends AuthBackendController $removeForm->addElement('button', 'btn_submit', array( 'escape' => false, 'type' => 'submit', - 'class' => 'link-like spinner', + 'class' => 'link-button spinner', 'value' => 'btn_submit', 'decorators' => array('ViewHelper'), 'label' => $this->view->icon('trash'), diff --git a/application/views/scripts/group/show.phtml b/application/views/scripts/group/show.phtml index 8d81683ee..fb850a2a5 100644 --- a/application/views/scripts/group/show.phtml +++ b/application/views/scripts/group/show.phtml @@ -23,90 +23,86 @@ if ($this->hasPermission('config/authentication/groups/edit') && $backend instan } ?> -
- compact): ?> - - -

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'); ?>

+
compact): ?> - sortBox; ?> + - limiter; ?> - paginator; ?> +

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); ?>
compact): ?> - filterEditor; ?> +

translate('Members'); ?>

+
+ limiter; ?> + paginator; ?> + sortBox; ?> +
+ filterEditor; ?>
-
+
qlink( - $this->translate('Add User to Group') , + $this->translate('Add New Member'), 'group/addmember', - null, array( - 'class' => 'button-link', - 'data-base-target' => '_next', - 'icon' => 'plus', - 'title' => $this->translate('Add user to group') + 'backend' => $backend->getName(), + 'group' => $group->group_name + ), + array( + 'icon' => 'plus', + 'class' => 'button-link' ) ) ?> - - - - - - - - - - - - - - - - - - - - - -hasResult()): ?> - -
translate('Username'); ?>translate('Remove'); ?>
- hasPermission('config/authentication/users/show') - && ($userBackend = $backend->getUserBackendName($member->user_name)) !== null - ): ?> - qlink($member->user_name, 'user/show', array( - 'backend' => $userBackend, - 'user' => $member->user_name - ), array( - 'title' => sprintf($this->translate('Show detailed information about %s'), $member->user_name) - )); ?> - - escape($member->user_name); ?> - - - getElement('user_name')->setValue($member->user_name); echo $removeForm; ?> -
- +hasResult()): ?>

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

-
+ + + + + + + + + + + + + + + + + + + + + +
translate('Username'); ?>translate('Remove'); ?>
+ hasPermission('config/authentication/users/show') + && ($userBackend = $backend->getUserBackendName($member->user_name)) !== null + ): ?> + qlink($member->user_name, 'user/show', array( + 'backend' => $userBackend, + 'user' => $member->user_name + ), array( + 'title' => sprintf($this->translate('Show detailed information about %s'), $member->user_name) + )); ?> + + escape($member->user_name); ?> + + + getElement('user_name')->setValue($member->user_name); echo $removeForm; ?> +
+
\ No newline at end of file diff --git a/public/css/icinga/main-content.less b/public/css/icinga/main-content.less index 05c41ce62..0d0e92d23 100644 --- a/public/css/icinga/main-content.less +++ b/public/css/icinga/main-content.less @@ -256,33 +256,6 @@ div.content.groups { } } -div.content.members { - table.member-list { - th.member-remove { - width: 8em; - padding-right: 0.5em; - text-align: right; - } - - td.member-remove { - text-align: right; - - form button.link-like { - color: inherit; - } - } - } - - p { - margin-top: 0; - } - - a.member-add { - display: block; - margin-top: 1em; - } -} - form.backend-selection { float: right; From 1144c869365323a773449241271c40e0b5a157f5 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 15 Oct 2015 16:14:19 +0200 Subject: [PATCH 13/84] group/list.phtml: Fix coding style, link targets, css, etc refs #10367 --- application/controllers/GroupController.php | 4 +- application/views/scripts/group/list.phtml | 134 ++++++++++---------- public/css/icinga/main-content.less | 23 ---- 3 files changed, 68 insertions(+), 93 deletions(-) diff --git a/application/controllers/GroupController.php b/application/controllers/GroupController.php index 3f14b2090..21fba5ebb 100644 --- a/application/controllers/GroupController.php +++ b/application/controllers/GroupController.php @@ -44,7 +44,7 @@ class GroupController extends AuthBackendController 'backend', array( 'autosubmit' => true, - 'label' => $this->translate('Usergroup Backend'), + 'label' => $this->translate('User Group Backend'), 'multiOptions' => array_combine($backendNames, $backendNames), 'value' => $this->params->get('backend') ) @@ -66,7 +66,7 @@ class GroupController extends AuthBackendController $this->setupLimitControl(); $this->setupSortControl( array( - 'group_name' => $this->translate('Group'), + 'group_name' => $this->translate('User Group'), 'created_at' => $this->translate('Created at'), 'last_modified' => $this->translate('Last modified') ), diff --git a/application/views/scripts/group/list.phtml b/application/views/scripts/group/list.phtml index 01bf3f7dd..b2bc9b920 100644 --- a/application/views/scripts/group/list.phtml +++ b/application/views/scripts/group/list.phtml @@ -4,30 +4,24 @@ use Icinga\Data\Extensible; use Icinga\Data\Reducible; if (! $this->compact): ?> -
- tabs; ?> -
-
- limiter ?> +
+ +
+ limiter ?> + paginator ?> + sortBox ?>
-
- paginator ?> +
+ backendSelection; ?> + filterEditor; ?>
-
- sortBox ?> -
-
-
- backendSelection; ?> - filterEditor; ?> -
-
+
translate('No backend found which is able to list groups') . '
'; + echo $this->translate('No backend found which is able to list user groups') . '
'; return; } else { $extensible = $this->hasPermission('config/authentication/groups/add') && $backend instanceof Extensible; @@ -37,64 +31,68 @@ if (! isset($backend)) { qlink( - $this->translate('Add a New User Group') , + $this->translate('Add a New User Group'), 'group/add', array('backend' => $backend->getName()), array( 'class' => 'button-link', 'data-base-target' => '_next', - 'icon' => 'plus', - 'title' => $this->translate('Create a new user group') + 'icon' => 'plus' ) ) ?> - - - - - - - - - - - - - - - - - - - - - -hasResult()): ?> - -
translate('Group'); ?>translate('Remove'); ?>
qlink($group->group_name, 'group/show', array( - 'backend' => $backend->getName(), - 'group' => $group->group_name - ), array( - 'title' => sprintf($this->translate('Show detailed information for group %s'), $group->group_name) - )); ?> - qlink( - null, - 'group/remove', - array( - 'backend' => $backend->getName(), - 'group' => $group->group_name - ), - array( - 'title' => sprintf($this->translate('Remove group %s'), $group->group_name), - 'icon' => 'trash' - ) - ); ?> -
- -

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

- +hasResult()): ?> +

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

+ + + + + + + + + + + + + + + + + + + + + +
translate('User Group'); ?>translate('Remove'); ?>
+ qlink( + $group->group_name, + 'group/show', + array( + 'backend' => $backend->getName(), + 'group' => $group->group_name + ), + array( + 'title' => sprintf( + $this->translate('Show detailed information for user group %s'), + $group->group_name + ) + ) + ); ?> + + qlink( + null, + 'group/remove', + array( + 'backend' => $backend->getName(), + 'group' => $group->group_name + ), + array( + 'title' => sprintf($this->translate('Remove user group %s'), $group->group_name), + 'icon' => 'trash' + ) + ); ?> +
+
\ No newline at end of file diff --git a/public/css/icinga/main-content.less b/public/css/icinga/main-content.less index 0d0e92d23..624b21eec 100644 --- a/public/css/icinga/main-content.less +++ b/public/css/icinga/main-content.less @@ -233,29 +233,6 @@ div.content.users { } } -div.content.groups { - table.group-list { - th.group-remove { - width: 8em; - padding-right: 0.5em; - text-align: right; - } - - td.group-remove { - text-align: right; - } - } - - p { - margin-top: 0; - } - - a.group-add { - display: block; - margin-top: 1em; - } -} - form.backend-selection { float: right; From 9ccd6cd95357f559292b3963f6bf578ed39c483f Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 15 Oct 2015 17:06:02 +0200 Subject: [PATCH 14/84] user/list.phtml: Fix coding style, link targets, css, etc refs #10367 --- application/controllers/UserController.php | 2 +- application/views/scripts/user/list.phtml | 52 +++++++++++----------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/application/controllers/UserController.php b/application/controllers/UserController.php index 9b2845758..69d4a71a5 100644 --- a/application/controllers/UserController.php +++ b/application/controllers/UserController.php @@ -44,7 +44,7 @@ class UserController extends AuthBackendController 'backend', array( 'autosubmit' => true, - 'label' => $this->translate('Authentication Backend'), + 'label' => $this->translate('User Backend'), 'multiOptions' => array_combine($backendNames, $backendNames), 'value' => $this->params->get('backend') ) diff --git a/application/views/scripts/user/list.phtml b/application/views/scripts/user/list.phtml index c89ea82e8..26114d657 100644 --- a/application/views/scripts/user/list.phtml +++ b/application/views/scripts/user/list.phtml @@ -1,20 +1,15 @@ compact): ?> -
- tabs ?> -
-
- limiter ?> -
-
- paginator ?> -
-
- sortBox ?> -
+
+ +
+ limiter ?> + paginator ?> + sortBox ?>
backendSelection ?> @@ -22,20 +17,18 @@ if (! $this->compact): ?>
-
- -

translate('No backend found which is able to list users.') ?>

-
- -hasResult()): ?> -

translate('No users found matching the filter.') ?>

-
- hasPermission('config/authentication/users/add') && $backend instanceof Extensible; -$reducible = $this->hasPermission('config/authentication/users/remove') && $backend instanceof Reducible; + +if (! isset($backend)) { + echo $this->translate('No backend found which is able to list users') . '
'; + return; +} else { + $extensible = $this->hasPermission('config/authentication/users/add') && $backend instanceof Extensible; + $reducible = $this->hasPermission('config/authentication/users/remove') && $backend instanceof Reducible; +} ?> + qlink( $this->translate('Add a New User') , @@ -44,12 +37,17 @@ $reducible = $this->hasPermission('config/authentication/users/remove') && $back array( 'class' => 'button-link', 'data-base-target' => '_next', - 'icon' => 'plus', - 'title' => $this->translate('Create a new user') + 'icon' => 'plus' ) ) ?> - + +hasResult()): ?> +

translate('No users found matching the filter') ?>

+ + + +
@@ -91,4 +89,4 @@ $reducible = $this->hasPermission('config/authentication/users/remove') && $back
translate('Username') ?>
-
+
\ No newline at end of file From 8db123ffa038fe0eec4645ae2cd439c7f5b8e747 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 15 Oct 2015 17:22:47 +0200 Subject: [PATCH 15/84] MacroTest: Remove obsolete tests The macro resolver does not know anymore about customvars, just plain object attributes, which are now covered by testFaultyMacros() --- .../library/Monitoring/Object/MacroTest.php | 32 ++----------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/modules/monitoring/test/php/library/Monitoring/Object/MacroTest.php b/modules/monitoring/test/php/library/Monitoring/Object/MacroTest.php index de90e1e81..6b2c30a53 100644 --- a/modules/monitoring/test/php/library/Monitoring/Object/MacroTest.php +++ b/modules/monitoring/test/php/library/Monitoring/Object/MacroTest.php @@ -44,41 +44,15 @@ class MacroTest extends BaseTestCase $this->assertEquals(Macro::resolveMacros('$service.description$', $svcMock), $svcMock->service_description); } - public function testCustomvars() - { - $objectMock = Mockery::mock('object'); - $objectMock->customvars = array( - 'customvar' => 'test' - ); - - $this->assertEquals(Macro::resolveMacros('$CUSTOMVAR$', $objectMock), $objectMock->customvars['customvar']); - } - public function testFaultyMacros() { $hostMock = Mockery::mock('host'); $hostMock->host_name = 'test'; - $hostMock->customvars = array( - 'host' => 'te', - 'name' => 'st' - ); + $hostMock->host = 'te'; $this->assertEquals( - Macro::resolveMacros('$$HOSTNAME$ $ HOSTNAME$ $HOST$NAME$', $hostMock), - '$test $ HOSTNAME$ teNAME$' - ); - } - - public function testMacrosWithSpecialCharacters() - { - $objectMock = Mockery::mock('object'); - $objectMock->customvars = array( - 'v€ry_sp3c|@l' => 'not too special!' - ); - - $this->assertEquals( - Macro::resolveMacros('$V€RY_SP3C|@L$', $objectMock), - $objectMock->customvars['v€ry_sp3c|@l'] + '$test $ HOSTNAME$ teNAME$', + Macro::resolveMacros('$$HOSTNAME$ $ HOSTNAME$ $host$NAME$', $hostMock) ); } } From 33037eebbbf06df23764897f31846af5ca0a1618 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 16 Oct 2015 10:08:14 +0200 Subject: [PATCH 16/84] Revert "Fix group base DN is erroneously used in place of user base DN" This reverts commit ac7546d9f2f166a3bebbbb9d5941b2084c1ce00b. --- .../Icinga/Authentication/UserGroup/LdapUserGroupBackend.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index 1b1bb31d0..b9827b40b 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -622,7 +622,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt return $this ->setGroupBaseDn($config->base_dn) - ->setUserBaseDn($config->get('user_base_dn', $this->getUserBaseDn())) + ->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)) From 7ef76932d4af652739f149e3eb104f7b3a61bd91 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 16 Oct 2015 12:36:47 +0200 Subject: [PATCH 17/84] DbRepository: Validate the table when inserting, updating and deleting --- library/Icinga/Authentication/User/DbUserBackend.php | 2 ++ library/Icinga/Repository/DbRepository.php | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/library/Icinga/Authentication/User/DbUserBackend.php b/library/Icinga/Authentication/User/DbUserBackend.php index c0c949fd7..e004a56c0 100644 --- a/library/Icinga/Authentication/User/DbUserBackend.php +++ b/library/Icinga/Authentication/User/DbUserBackend.php @@ -130,6 +130,7 @@ class DbUserBackend extends DbRepository implements UserBackendInterface, Inspec */ public function insert($table, array $bind) { + $this->requireTable($table); $bind['created_at'] = date('Y-m-d H:i:s'); $this->ds->insert( $this->prependTablePrefix($table), @@ -150,6 +151,7 @@ class DbUserBackend extends DbRepository implements UserBackendInterface, Inspec */ public function update($table, array $bind, Filter $filter = null) { + $this->requireTable($table); $bind['last_modified'] = date('Y-m-d H:i:s'); if ($filter) { $filter = $this->requireFilter($table, $filter); diff --git a/library/Icinga/Repository/DbRepository.php b/library/Icinga/Repository/DbRepository.php index a187d76c0..90b7807fa 100644 --- a/library/Icinga/Repository/DbRepository.php +++ b/library/Icinga/Repository/DbRepository.php @@ -297,6 +297,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, */ public function insert($table, array $bind) { + $this->requireTable($table); $this->ds->insert($this->prependTablePrefix($table), $this->requireStatementColumns($table, $bind)); } @@ -309,6 +310,8 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, */ public function update($table, array $bind, Filter $filter = null) { + $this->requireTable($table); + if ($filter) { $filter = $this->requireFilter($table, $filter); } @@ -324,6 +327,8 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, */ public function delete($table, Filter $filter = null) { + $this->requireTable($table); + if ($filter) { $filter = $this->requireFilter($table, $filter); } From 1b7dc1098c0c26932ec634dfeab68dc15e4d7ce6 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 16 Oct 2015 13:10:39 +0200 Subject: [PATCH 18/84] DbUserGroupBackend: Use LEFT JOIN to join the group_membership table Fixes the issue that groups are not found if they do not have any members even though they meet the where clause --- library/Icinga/Authentication/UserGroup/DbUserGroupBackend.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Authentication/UserGroup/DbUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/DbUserGroupBackend.php index 5596f18b6..6f5c2e9dd 100644 --- a/library/Icinga/Authentication/UserGroup/DbUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/DbUserGroupBackend.php @@ -250,7 +250,7 @@ class DbUserGroupBackend extends DbRepository implements UserGroupBackendInterfa */ protected function joinGroupMembership(RepositoryQuery $query) { - $query->getQuery()->join( + $query->getQuery()->joinLeft( $this->requireTable('group_membership'), 'g.id = gm.group_id', array() From 58fc87b2e56e2cd3c16d33ddf41c7cdc469b430e Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 16 Oct 2015 14:46:44 +0200 Subject: [PATCH 19/84] Repository: Ensure that we'll internally only work with virtual table names refs #10367 --- .../Authentication/User/LdapUserBackend.php | 46 +++---- .../UserGroup/LdapUserGroupBackend.php | 26 ++-- library/Icinga/Repository/DbRepository.php | 120 +++++------------- library/Icinga/Repository/RepositoryQuery.php | 5 +- 4 files changed, 74 insertions(+), 123 deletions(-) diff --git a/library/Icinga/Authentication/User/LdapUserBackend.php b/library/Icinga/Authentication/User/LdapUserBackend.php index d8ea19ca2..f3a852115 100644 --- a/library/Icinga/Authentication/User/LdapUserBackend.php +++ b/library/Icinga/Authentication/User/LdapUserBackend.php @@ -102,15 +102,13 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In /** * Set the objectClass where to look for users * - * Sets also the base table name for the underlying repository. - * * @param string $userClass * * @return $this */ public function setUserClass($userClass) { - $this->baseTable = $this->userClass = $this->getNormedAttribute($userClass); + $this->userClass = $this->getNormedAttribute($userClass); return $this; } @@ -216,13 +214,10 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In * * @return array * - * @throws ProgrammingError In case either $this->userNameAttribute or $this->userClass has not been set yet + * @throws ProgrammingError In case $this->userNameAttribute has not been set yet */ protected function initializeQueryColumns() { - if ($this->userClass === null) { - throw new ProgrammingError('It is required to set the objectClass where to look for users first'); - } if ($this->userNameAttribute === null) { throw new ProgrammingError('It is required to set a attribute name where to find a user\'s name first'); } @@ -240,7 +235,7 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In } return array( - $this->userClass => array( + 'user' => array( 'user' => $this->userNameAttribute, 'user_name' => $this->userNameAttribute, 'is_active' => $isActiveAttribute, @@ -269,15 +264,9 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In * Initialize this repository's conversion rules * * @return array - * - * @throws ProgrammingError In case $this->userClass has not been set yet */ protected function initializeConversionRules() { - if ($this->userClass === null) { - throw new ProgrammingError('It is required to set the objectClass where to look for users first'); - } - if ($this->ds->getCapabilities()->isActiveDirectory()) { $stateConverter = 'user_account_control'; } else { @@ -285,7 +274,7 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In } return array( - $this->userClass => array( + 'user' => array( 'is_active' => $stateConverter, 'created_at' => 'generalized_time', 'last_modified' => 'generalized_time' @@ -330,14 +319,26 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In } /** - - * @param Inspection $info Optional inspection to fill with diagnostic info + * Validate that the requested table exists * - * @throws AuthenticationException When authentication is not possible + * This will return $this->userClass in case $table equals "user". + * + * @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 assertAuthenticationPossible(Inspection $insp = null) + public function requireTable($table, RepositoryQuery $query = null) { + $table = parent::requireTable($table, $query); + if ($table === 'user') { + $table = $this->userClass; + } + return $table; } /** @@ -359,17 +360,16 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In ->getQuery() ->setUsePagedResults(false) ->fetchDn(); - if ($userDn === null) { return false; } - $testCredentialsResult = $this->ds->testCredentials($userDn, $password); - if ($testCredentialsResult) { + $validCredentials = $this->ds->testCredentials($userDn, $password); + if ($validCredentials) { $user->setAdditional('ldap_dn', $userDn); } - return $testCredentialsResult; + return $validCredentials; } catch (LdapException $e) { throw new AuthenticationException( 'Failed to authenticate user "%s" against backend "%s". An exception was thrown:', diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index b9827b40b..648c6c700 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -211,15 +211,13 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt /** * 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); + $this->groupClass = $this->getNormedAttribute($groupClass); return $this; } @@ -382,16 +380,17 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt * * @return array * - * @throws ProgrammingError In case either $this->groupNameAttribute or $this->groupClass has not been set yet + * @throws ProgrammingError In case either $this->groupNameAttribute or + * $this->groupMemberAttribute 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->groupMemberAttribute === null) { + throw new ProgrammingError('It is required to set a attribute name where to find a group\'s members first'); + } if ($this->ds->getCapabilities()->isActiveDirectory()) { $createdAtAttribute = 'whenCreated'; @@ -409,7 +408,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt 'created_at' => $createdAtAttribute, 'last_modified' => $lastModifiedAttribute ); - return array($this->groupClass => $columns, $this->groupClass => $columns); + return array('group' => $columns, 'group_membership' => $columns); } /** @@ -432,7 +431,8 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt * * @return array * - * @throws ProgrammingError In case $this->groupClass has not been set yet + * @throws ProgrammingError In case either $this->groupClass or $this->groupMemberAttribute + * has not been set yet */ protected function initializeConversionRules() { @@ -444,13 +444,17 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt } $rules = array( - $this->groupClass => array( + 'group' => array( + 'created_at' => 'generalized_time', + 'last_modified' => 'generalized_time' + ), + 'group_membership' => array( 'created_at' => 'generalized_time', 'last_modified' => 'generalized_time' ) ); if (! $this->isAmbiguous($this->groupClass, $this->groupMemberAttribute)) { - $rules[$this->groupClass][] = 'user_name'; + $rules['group']['user_name'] = 'user_name'; } return $rules; diff --git a/library/Icinga/Repository/DbRepository.php b/library/Icinga/Repository/DbRepository.php index 90b7807fa..27144d097 100644 --- a/library/Icinga/Repository/DbRepository.php +++ b/library/Icinga/Repository/DbRepository.php @@ -473,8 +473,8 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, * This does not check whether any conversion for the given table is available if $column is not given, as it * may be possible that columns from another table where joined in which would otherwise not being converted. * - * @param array|string $table - * @param string $column + * @param string $table + * @param string $column * * @return bool */ @@ -482,10 +482,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, { if ($column !== null) { if ($this->validateQueryColumnAssociation($table, $column)) { - return parent::providesValueConversion( - $this->removeTablePrefix($this->clearTableAlias($table)), - $column - ); + return parent::providesValueConversion($table, $column); } if (($tableName = $this->findTableName($column))) { @@ -518,11 +515,9 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, protected function getConverter($table, $name, $context, RepositoryQuery $query = null) { if ( - ($query !== null && $this->validateQueryColumnAssociation($table, $name)) - || ($query === null && $this->validateStatementColumnAssociation($table, $name)) + ! ($query !== null && $this->validateQueryColumnAssociation($table, $name)) + && !($query === null && $this->validateStatementColumnAssociation($table, $name)) ) { - $table = $this->removeTablePrefix($this->clearTableAlias($table)); - } else { $table = $this->findTableName($name); if (! $table) { throw new ProgrammingError('Column name validation seems to have failed. Did you require the column?'); @@ -589,44 +584,17 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, return $filter; } - /** - * Return this repository's query columns of the given table mapped to their respective aliases - * - * @param array|string $table - * - * @return array - * - * @throws ProgrammingError In case $table does not exist - */ - public function requireAllQueryColumns($table) - { - return parent::requireAllQueryColumns($this->removeTablePrefix($this->clearTableAlias($table))); - } - - /** - * Return the query column name for the given alias or null in case the alias does not exist - * - * @param array|string $table - * @param string $alias - * - * @return string|null - */ - public function resolveQueryColumnAlias($table, $alias) - { - return parent::resolveQueryColumnAlias($this->removeTablePrefix($this->clearTableAlias($table)), $alias); - } - /** * Return the alias for the given query column name or null in case the query column name does not exist * - * @param array|string $table - * @param string $column + * @param string $table + * @param string $column * * @return string|null */ public function reassembleQueryColumnAlias($table, $column) { - $alias = parent::reassembleQueryColumnAlias($this->removeTablePrefix($this->clearTableAlias($table)), $column); + $alias = parent::reassembleQueryColumnAlias($table, $column); if ( $alias === null && !$this->validateQueryColumnAssociation($table, $column) @@ -638,29 +606,13 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, return $alias; } - /** - * Return whether the given query column name or alias is available in the given table - * - * @param array|string $table - * @param string $column - * - * @return bool - */ - public function validateQueryColumnAssociation($table, $column) - { - return parent::validateQueryColumnAssociation( - $this->removeTablePrefix($this->clearTableAlias($table)), - $column - ); - } - /** * Validate that the given column is a valid query target and return it or the actual name if it's an alias * * Attempts to join the given column from a different table if its association to the given table cannot be * verified. * - * @param array|string $table The table where to look for the column or alias + * @param string $table The table where to look for the column or alias * @param string $name The name or alias of the column to validate * @param RepositoryQuery $query An optional query to pass as context, * if not given no join will be attempted @@ -672,7 +624,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, public function requireQueryColumn($table, $name, RepositoryQuery $query = null) { if ($query === null || $this->validateQueryColumnAssociation($table, $name)) { - return parent::requireQueryColumn($this->removeTablePrefix($this->clearTableAlias($table)), $name, $query); + return parent::requireQueryColumn($table, $name, $query); } return $this->joinColumn($name, $table, $query); @@ -684,7 +636,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, * Attempts to join the given column from a different table if its association to the given table cannot be * verified. * - * @param array|string $table The table where to look for the column or alias + * @param string $table The table where to look for the column or alias * @param string $name The name or alias of the column to validate * @param RepositoryQuery $query An optional query to pass as context, * if not given the column is considered being used for a statement filter @@ -700,7 +652,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, } if ($this->validateQueryColumnAssociation($table, $name)) { - return parent::requireFilterColumn($this->removeTablePrefix($this->clearTableAlias($table)), $name, $query); + return parent::requireFilterColumn($table, $name, $query); } return $this->joinColumn($name, $table, $query); @@ -709,8 +661,8 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, /** * Return the statement column name for the given alias or null in case the alias does not exist * - * @param array|string $table - * @param string $alias + * @param string $table + * @param string $alias * * @return string|null */ @@ -721,7 +673,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, return $statementAliasColumnMap[$alias]; } - $prefixedAlias = $this->removeTablePrefix($this->clearTableAlias($table)) . '.' . $alias; + $prefixedAlias = $table . '.' . $alias; if (isset($statementAliasColumnMap[$prefixedAlias])) { return $statementAliasColumnMap[$prefixedAlias]; } @@ -730,8 +682,8 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, /** * Return the alias for the given statement column name or null in case the statement column does not exist * - * @param array|string $table - * @param string $column + * @param string $table + * @param string $column * * @return string|null */ @@ -742,7 +694,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, return $statementColumnAliasMap[$column]; } - $prefixedColumn = $this->removeTablePrefix($this->clearTableAlias($table)) . '.' . $column; + $prefixedColumn = $table . '.' . $column; if (isset($statementColumnAliasMap[$prefixedColumn])) { return $statementColumnAliasMap[$prefixedColumn]; } @@ -751,34 +703,32 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, /** * Return whether the given alias or statement column name is available in the given table * - * @param array|string $table - * @param string $alias + * @param string $table + * @param string $alias * * @return bool */ public function validateStatementColumnAssociation($table, $alias) { - $tableName = $this->removeTablePrefix($this->clearTableAlias($table)); - $statementAliasTableMap = $this->getStatementAliasTableMap(); if (isset($statementAliasTableMap[$alias])) { - return $statementAliasTableMap[$alias] === $tableName; + return $statementAliasTableMap[$alias] === $table; } $statementColumnTableMap = $this->getStatementColumnTableMap(); if (isset($statementColumnTableMap[$alias])) { - return $statementColumnTableMap[$alias] === $tableName; + return $statementColumnTableMap[$alias] === $table; } - $prefixedAlias = $tableName . '.' . $alias; + $prefixedAlias = $table . '.' . $alias; return isset($statementAliasTableMap[$prefixedAlias]) || isset($statementColumnTableMap[$prefixedAlias]); } /** * Return whether the given column name or alias of the given table is a valid statement column * - * @param array|string $table The table where to look for the column or alias - * @param string $name The column name or alias to check + * @param string $table The table where to look for the column or alias + * @param string $name The column name or alias to check * * @return bool */ @@ -789,7 +739,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, && $this->reassembleStatementColumnAlias($table, $name) === null) || !$this->validateStatementColumnAssociation($table, $name) ) { - return parent::hasStatementColumn($this->removeTablePrefix($this->clearTableAlias($table)), $name); + return parent::hasStatementColumn($table, $name); } return true; @@ -798,8 +748,8 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, /** * Validate that the given column is a valid statement column and return it or the actual name if it's an alias * - * @param array|string $table The table for which to require the column - * @param string $name The name or alias of the column to validate + * @param string $table The table for which to require the column + * @param string $name The name or alias of the column to validate * * @return string The given column's name * @@ -812,15 +762,11 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, } elseif (($alias = $this->reassembleStatementColumnAlias($table, $name)) !== null) { $column = $name; } else { - return parent::requireStatementColumn($this->removeTablePrefix($this->clearTableAlias($table)), $name); + return parent::requireStatementColumn($table, $name); } if (! $this->validateStatementColumnAssociation($table, $alias)) { - throw new StatementException( - 'Statement column "%s" not found in table "%s"', - $name, - $this->removeTablePrefix($this->clearTableAlias($table)) - ); + throw new StatementException('Statement column "%s" not found in table "%s"', $name, $table); } return $column; @@ -834,7 +780,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, * The method is called with the same parameters but in reversed order. * * @param string $name The alias or column name to join into $target - * @param array|string $target The table to join $name into + * @param string $target The table to join $name into * @param RepositoryQUery $query The query to apply the JOIN-clause on * * @return string The resolved alias or $name @@ -848,7 +794,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, throw new ProgrammingError( 'Unable to find a valid table for column "%s" to join into "%s"', $name, - $this->removeTablePrefix($this->clearTableAlias($target)) + $target ); } @@ -866,7 +812,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, throw new ProgrammingError( 'Unable to join table "%s" into "%s". Method "%s" not found', $tableName, - $this->removeTablePrefix($this->clearTableAlias($target)), + $target, $joinMethod ); } diff --git a/library/Icinga/Repository/RepositoryQuery.php b/library/Icinga/Repository/RepositoryQuery.php index 927f4f02b..8beba988b 100644 --- a/library/Icinga/Repository/RepositoryQuery.php +++ b/library/Icinga/Repository/RepositoryQuery.php @@ -79,8 +79,9 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera */ public function from($target, array $columns = null) { - $target = $this->repository->requireTable($target, $this); - $this->query = $this->repository->getDataSource()->select()->from($target); + $this->query = $this->repository->getDataSource()->select()->from( + $this->repository->requireTable($target, $this) + ); $this->query->columns($this->prepareQueryColumns($target, $columns)); $this->target = $target; return $this; From 8ed489c637de20a3f8810362240eb0ac8174a79f Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 16 Oct 2015 15:28:44 +0200 Subject: [PATCH 20/84] LdapUserGroupBackend: Add method persistUserName() refs #10367 refs #10370 --- .../UserGroup/LdapUserGroupBackend.php | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index 648c6c700..1521ba119 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -5,6 +5,7 @@ namespace Icinga\Authentication\UserGroup; use Icinga\Authentication\User\UserBackend; use Icinga\Authentication\User\LdapUserBackend; +use Icinga\Application\Logger; use Icinga\Data\ConfigObject; use Icinga\Exception\ConfigurationError; use Icinga\Exception\ProgrammingError; @@ -454,12 +455,48 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt ) ); if (! $this->isAmbiguous($this->groupClass, $this->groupMemberAttribute)) { + $rules['group_membership']['user_name'] = 'user_name'; + $rules['group_membership']['user'] = 'user_name'; $rules['group']['user_name'] = 'user_name'; + $rules['group']['user'] = 'user_name'; } return $rules; } + /** + * Return the distinguished name for the given uid or gid + * + * @param string $name + * + * @return string + */ + protected function persistUserName($name) + { + $userDn = $this->ds + ->select() + ->from($this->userClass, array()) + ->where($this->userNameAttribute, $name) + ->setUsePagedResults(false) + ->fetchDn(); + if ($userDn) { + return $userDn; + } + + $groupDn = $this->ds + ->select() + ->from($this->groupClass, array()) + ->where($this->groupNameAttribute, $name) + ->setUsePagedResults(false) + ->fetchDn(); + if ($groupDn) { + return $groupDn; + } + + Logger::debug('Unable to persist uid or gid "%s" in repository "%s". No DN found.', $name, $this->getName()); + return $name; + } + /** * Return the uid for the given distinguished name * From 6c313b816e4a7da125d4247a8828b0dd19d60bc8 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 16 Oct 2015 16:03:25 +0200 Subject: [PATCH 21/84] hostssummary.phtml: Fix copy&paste mistakes fixes #10389 --- .../views/scripts/list/components/hostssummary.phtml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml b/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml index eaa8fe363..ab1695765 100644 --- a/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml +++ b/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml @@ -73,8 +73,8 @@ $stateBadges 'host_state' => 2, 'host_handled' => 1 ), - 'List %u host that is currently in state UNREACHABLE (Acknowledged) in the host group "%s"', - 'List %u hosts which are currently in state UNREACHABLE (Acknowledged) in the host group "%s"', + 'List %u host that is currently in state UNREACHABLE (Acknowledged)', + 'List %u hosts which are currently in state UNREACHABLE (Acknowledged)', array($stats->hosts_unreachable_handled) ) ->add( @@ -83,8 +83,8 @@ $stateBadges array( 'host_state' => 99 ), - 'List %u host that is currently in state UNREACHABLE (Acknowledged)', - 'List %u hosts which are currently in state UNREACHABLE (Acknowledged)', + 'List %u host that is currently in state PENDING', + 'List %u hosts which are currently in state PENDING', array($stats->hosts_pending) ); echo $stateBadges->render(); From 878bd78587f088248e49f751b1808ce31d6887a1 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 16 Oct 2015 17:25:42 +0200 Subject: [PATCH 22/84] LdapUserBackend: Unfold the user_name_attribute automatically This is.. the currently easiest solution. As long as attribute unfolding is not very performance intensive this solution suffices. refs #10367 refs #10332 --- .../Authentication/User/LdapUserBackend.php | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/library/Icinga/Authentication/User/LdapUserBackend.php b/library/Icinga/Authentication/User/LdapUserBackend.php index f3a852115..b5bd51f45 100644 --- a/library/Icinga/Authentication/User/LdapUserBackend.php +++ b/library/Icinga/Authentication/User/LdapUserBackend.php @@ -341,6 +341,27 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In return $table; } + /** + * Validate that the given column is a valid query target and return it or the actual name if it's an alias + * + * @param string $table The table where to look for the column or alias + * @param string $name The name or alias of the column to validate + * @param RepositoryQuery $query An optional query to pass as context + * + * @return string The given column's name + * + * @throws QueryException In case the given column is not a valid query column + */ + public function requireQueryColumn($table, $name, RepositoryQuery $query = null) + { + $column = parent::requireQueryColumn($table, $name, $query); + if ($name === 'user_name' && $query !== null) { + $query->getQuery()->setUnfoldAttribute('user_name'); + } + + return $column; + } + /** * Authenticate the given user * From 33956e02f82d638b3202bd0e3dc726760cc6d9ad Mon Sep 17 00:00:00 2001 From: Markus Frosch Date: Tue, 20 Oct 2015 10:02:42 +0200 Subject: [PATCH 23/84] Fix collection of user_base_dn from the UserBackend Currently the group_base_dn is used, unless a user_base_dn is configured in the group backend. refs #10402 --- .../Icinga/Authentication/UserGroup/LdapUserGroupBackend.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index 65187dcd4..23dd4d6c9 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -607,7 +607,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt return $this ->setGroupBaseDn($config->base_dn) - ->setUserBaseDn($config->get('user_base_dn', $this->getGroupBaseDn())) + ->setUserBaseDn($config->get('user_base_dn', $defaults->user_base_dn)) ->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)) From 0b9a1415916fd295f7abf18c3951626234d54b35 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 20 Oct 2015 11:28:18 +0200 Subject: [PATCH 24/84] LdapUserGroupBackend: Use the group_base_dn as user_base_dn.. ..if neither the config nor the defaults provide a value. refs #10402 --- .../Icinga/Authentication/UserGroup/LdapUserGroupBackend.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index 23dd4d6c9..a25f8ad96 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -607,7 +607,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt return $this ->setGroupBaseDn($config->base_dn) - ->setUserBaseDn($config->get('user_base_dn', $defaults->user_base_dn)) + ->setUserBaseDn($config->get('user_base_dn', $defaults->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)) From 6707bb990c304c86f339addb42dbc0eceb185983 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 21 Oct 2015 15:45:36 +0200 Subject: [PATCH 25/84] puppet: Do not replace the authentication.ini if already existing --- .puppet/profiles/icingaweb2_dev/manifests/init.pp | 1 + 1 file changed, 1 insertion(+) diff --git a/.puppet/profiles/icingaweb2_dev/manifests/init.pp b/.puppet/profiles/icingaweb2_dev/manifests/init.pp index 33f29df3b..150838d5e 100644 --- a/.puppet/profiles/icingaweb2_dev/manifests/init.pp +++ b/.puppet/profiles/icingaweb2_dev/manifests/init.pp @@ -88,6 +88,7 @@ class icingaweb2_dev ( icingaweb2::config::general { 'authentication': source => $name, + replace => false, } icingaweb2::config::general { [ 'config', 'resources', 'roles' ]: From 20e010bea07df8a37585c9459d999671068d1fbd Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 22 Oct 2015 09:44:02 +0200 Subject: [PATCH 26/84] doc: Fix upgrade documentation for 2.0.0 --- doc/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/installation.md b/doc/installation.md index cf1d444aa..56bf92732 100644 --- a/doc/installation.md +++ b/doc/installation.md @@ -495,6 +495,6 @@ The first release candidate of Icinga Web 2 introduces the following non-backwar the file remains unchanged. * The location of a user's preferences has been changed from - **/preferences/.ini** to - **/preferences//config.ini**. + **config-dir/preferences/username.ini** to + **config-dir/preferences/username/config.ini**. The content of the file remains unchanged. From 6b0b7fbeeac876be6ce41d44d7078eb69aa0f296 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 22 Oct 2015 16:29:37 +0200 Subject: [PATCH 27/84] Repair redirect after login fixes #10287 --- application/forms/Authentication/LoginForm.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/forms/Authentication/LoginForm.php b/application/forms/Authentication/LoginForm.php index bb01f8a04..6958def01 100644 --- a/application/forms/Authentication/LoginForm.php +++ b/application/forms/Authentication/LoginForm.php @@ -71,7 +71,7 @@ class LoginForm extends Form if ($this->created) { $redirect = $this->getElement('redirect')->getValue(); } - if (empty($redirect) || strpos($redirect, 'authentication/logout') !== 0) { + if (empty($redirect) || strpos($redirect, 'authentication/logout') === 0) { $redirect = static::REDIRECT_URL; } return Url::fromPath($redirect); From fdb186ec565a098167775e982175e3928f0669f6 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 22 Oct 2015 16:55:40 +0200 Subject: [PATCH 28/84] Revert "Repair redirect after login" This reverts commit 6b0b7fbeeac876be6ce41d44d7078eb69aa0f296. --- application/forms/Authentication/LoginForm.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/forms/Authentication/LoginForm.php b/application/forms/Authentication/LoginForm.php index 6958def01..bb01f8a04 100644 --- a/application/forms/Authentication/LoginForm.php +++ b/application/forms/Authentication/LoginForm.php @@ -71,7 +71,7 @@ class LoginForm extends Form if ($this->created) { $redirect = $this->getElement('redirect')->getValue(); } - if (empty($redirect) || strpos($redirect, 'authentication/logout') === 0) { + if (empty($redirect) || strpos($redirect, 'authentication/logout') !== 0) { $redirect = static::REDIRECT_URL; } return Url::fromPath($redirect); From da744caaef13b409f0ad5267aee76dc832329999 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 22 Oct 2015 16:29:37 +0200 Subject: [PATCH 29/84] Repair redirect after login fixes #10287 --- application/forms/Authentication/LoginForm.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/forms/Authentication/LoginForm.php b/application/forms/Authentication/LoginForm.php index bb01f8a04..190fd06f5 100644 --- a/application/forms/Authentication/LoginForm.php +++ b/application/forms/Authentication/LoginForm.php @@ -71,7 +71,7 @@ class LoginForm extends Form if ($this->created) { $redirect = $this->getElement('redirect')->getValue(); } - if (empty($redirect) || strpos($redirect, 'authentication/logout') !== 0) { + if (empty($redirect) || strpos($redirect, 'authentication/logout') !== false) { $redirect = static::REDIRECT_URL; } return Url::fromPath($redirect); From f88bd525f17568846f240a7f452f6248af0b821d Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Wed, 14 Oct 2015 15:12:20 +0200 Subject: [PATCH 30/84] DbConnection: respect charset parameter refs #10359 --- library/Icinga/Data/Db/DbConnection.php | 1 + 1 file changed, 1 insertion(+) diff --git a/library/Icinga/Data/Db/DbConnection.php b/library/Icinga/Data/Db/DbConnection.php index e62e24464..9abf9c437 100644 --- a/library/Icinga/Data/Db/DbConnection.php +++ b/library/Icinga/Data/Db/DbConnection.php @@ -130,6 +130,7 @@ class DbConnection implements Selectable, Extensible, Updatable, Reducible, Insp 'username' => $this->config->username, 'password' => $this->config->password, 'dbname' => $this->config->dbname, + 'charset' => $this->config->charset, 'persistent' => (bool) $this->config->get('persistent', false), 'options' => & $genericAdapterOptions, 'driver_options' => & $driverOptions From 36340aafa68f66f7d2709dd3f63125209410c947 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 16 Oct 2015 14:46:44 +0200 Subject: [PATCH 31/84] Repository: Ensure that we'll internally only work with virtual table names refs #10367 --- .../Authentication/User/LdapUserBackend.php | 46 +++---- .../UserGroup/LdapUserGroupBackend.php | 26 ++-- library/Icinga/Repository/DbRepository.php | 120 +++++------------- library/Icinga/Repository/RepositoryQuery.php | 5 +- 4 files changed, 74 insertions(+), 123 deletions(-) diff --git a/library/Icinga/Authentication/User/LdapUserBackend.php b/library/Icinga/Authentication/User/LdapUserBackend.php index d8ea19ca2..f3a852115 100644 --- a/library/Icinga/Authentication/User/LdapUserBackend.php +++ b/library/Icinga/Authentication/User/LdapUserBackend.php @@ -102,15 +102,13 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In /** * Set the objectClass where to look for users * - * Sets also the base table name for the underlying repository. - * * @param string $userClass * * @return $this */ public function setUserClass($userClass) { - $this->baseTable = $this->userClass = $this->getNormedAttribute($userClass); + $this->userClass = $this->getNormedAttribute($userClass); return $this; } @@ -216,13 +214,10 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In * * @return array * - * @throws ProgrammingError In case either $this->userNameAttribute or $this->userClass has not been set yet + * @throws ProgrammingError In case $this->userNameAttribute has not been set yet */ protected function initializeQueryColumns() { - if ($this->userClass === null) { - throw new ProgrammingError('It is required to set the objectClass where to look for users first'); - } if ($this->userNameAttribute === null) { throw new ProgrammingError('It is required to set a attribute name where to find a user\'s name first'); } @@ -240,7 +235,7 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In } return array( - $this->userClass => array( + 'user' => array( 'user' => $this->userNameAttribute, 'user_name' => $this->userNameAttribute, 'is_active' => $isActiveAttribute, @@ -269,15 +264,9 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In * Initialize this repository's conversion rules * * @return array - * - * @throws ProgrammingError In case $this->userClass has not been set yet */ protected function initializeConversionRules() { - if ($this->userClass === null) { - throw new ProgrammingError('It is required to set the objectClass where to look for users first'); - } - if ($this->ds->getCapabilities()->isActiveDirectory()) { $stateConverter = 'user_account_control'; } else { @@ -285,7 +274,7 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In } return array( - $this->userClass => array( + 'user' => array( 'is_active' => $stateConverter, 'created_at' => 'generalized_time', 'last_modified' => 'generalized_time' @@ -330,14 +319,26 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In } /** - - * @param Inspection $info Optional inspection to fill with diagnostic info + * Validate that the requested table exists * - * @throws AuthenticationException When authentication is not possible + * This will return $this->userClass in case $table equals "user". + * + * @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 assertAuthenticationPossible(Inspection $insp = null) + public function requireTable($table, RepositoryQuery $query = null) { + $table = parent::requireTable($table, $query); + if ($table === 'user') { + $table = $this->userClass; + } + return $table; } /** @@ -359,17 +360,16 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In ->getQuery() ->setUsePagedResults(false) ->fetchDn(); - if ($userDn === null) { return false; } - $testCredentialsResult = $this->ds->testCredentials($userDn, $password); - if ($testCredentialsResult) { + $validCredentials = $this->ds->testCredentials($userDn, $password); + if ($validCredentials) { $user->setAdditional('ldap_dn', $userDn); } - return $testCredentialsResult; + return $validCredentials; } catch (LdapException $e) { throw new AuthenticationException( 'Failed to authenticate user "%s" against backend "%s". An exception was thrown:', diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index a25f8ad96..2aa615a84 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -211,15 +211,13 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt /** * 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); + $this->groupClass = $this->getNormedAttribute($groupClass); return $this; } @@ -382,16 +380,17 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt * * @return array * - * @throws ProgrammingError In case either $this->groupNameAttribute or $this->groupClass has not been set yet + * @throws ProgrammingError In case either $this->groupNameAttribute or + * $this->groupMemberAttribute 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->groupMemberAttribute === null) { + throw new ProgrammingError('It is required to set a attribute name where to find a group\'s members first'); + } if ($this->ds->getCapabilities()->isActiveDirectory()) { $createdAtAttribute = 'whenCreated'; @@ -409,7 +408,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt 'created_at' => $createdAtAttribute, 'last_modified' => $lastModifiedAttribute ); - return array($this->groupClass => $columns, $this->groupClass => $columns); + return array('group' => $columns, 'group_membership' => $columns); } /** @@ -432,7 +431,8 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt * * @return array * - * @throws ProgrammingError In case $this->groupClass has not been set yet + * @throws ProgrammingError In case either $this->groupClass or $this->groupMemberAttribute + * has not been set yet */ protected function initializeConversionRules() { @@ -444,13 +444,17 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt } $rules = array( - $this->groupClass => array( + 'group' => array( + 'created_at' => 'generalized_time', + 'last_modified' => 'generalized_time' + ), + 'group_membership' => array( 'created_at' => 'generalized_time', 'last_modified' => 'generalized_time' ) ); if (! $this->isAmbiguous($this->groupClass, $this->groupMemberAttribute)) { - $rules[$this->groupClass][] = 'user_name'; + $rules['group']['user_name'] = 'user_name'; } return $rules; diff --git a/library/Icinga/Repository/DbRepository.php b/library/Icinga/Repository/DbRepository.php index a187d76c0..89701cb9d 100644 --- a/library/Icinga/Repository/DbRepository.php +++ b/library/Icinga/Repository/DbRepository.php @@ -468,8 +468,8 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, * This does not check whether any conversion for the given table is available if $column is not given, as it * may be possible that columns from another table where joined in which would otherwise not being converted. * - * @param array|string $table - * @param string $column + * @param string $table + * @param string $column * * @return bool */ @@ -477,10 +477,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, { if ($column !== null) { if ($this->validateQueryColumnAssociation($table, $column)) { - return parent::providesValueConversion( - $this->removeTablePrefix($this->clearTableAlias($table)), - $column - ); + return parent::providesValueConversion($table, $column); } if (($tableName = $this->findTableName($column))) { @@ -513,11 +510,9 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, protected function getConverter($table, $name, $context, RepositoryQuery $query = null) { if ( - ($query !== null && $this->validateQueryColumnAssociation($table, $name)) - || ($query === null && $this->validateStatementColumnAssociation($table, $name)) + ! ($query !== null && $this->validateQueryColumnAssociation($table, $name)) + && !($query === null && $this->validateStatementColumnAssociation($table, $name)) ) { - $table = $this->removeTablePrefix($this->clearTableAlias($table)); - } else { $table = $this->findTableName($name); if (! $table) { throw new ProgrammingError('Column name validation seems to have failed. Did you require the column?'); @@ -584,44 +579,17 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, return $filter; } - /** - * Return this repository's query columns of the given table mapped to their respective aliases - * - * @param array|string $table - * - * @return array - * - * @throws ProgrammingError In case $table does not exist - */ - public function requireAllQueryColumns($table) - { - return parent::requireAllQueryColumns($this->removeTablePrefix($this->clearTableAlias($table))); - } - - /** - * Return the query column name for the given alias or null in case the alias does not exist - * - * @param array|string $table - * @param string $alias - * - * @return string|null - */ - public function resolveQueryColumnAlias($table, $alias) - { - return parent::resolveQueryColumnAlias($this->removeTablePrefix($this->clearTableAlias($table)), $alias); - } - /** * Return the alias for the given query column name or null in case the query column name does not exist * - * @param array|string $table - * @param string $column + * @param string $table + * @param string $column * * @return string|null */ public function reassembleQueryColumnAlias($table, $column) { - $alias = parent::reassembleQueryColumnAlias($this->removeTablePrefix($this->clearTableAlias($table)), $column); + $alias = parent::reassembleQueryColumnAlias($table, $column); if ( $alias === null && !$this->validateQueryColumnAssociation($table, $column) @@ -633,29 +601,13 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, return $alias; } - /** - * Return whether the given query column name or alias is available in the given table - * - * @param array|string $table - * @param string $column - * - * @return bool - */ - public function validateQueryColumnAssociation($table, $column) - { - return parent::validateQueryColumnAssociation( - $this->removeTablePrefix($this->clearTableAlias($table)), - $column - ); - } - /** * Validate that the given column is a valid query target and return it or the actual name if it's an alias * * Attempts to join the given column from a different table if its association to the given table cannot be * verified. * - * @param array|string $table The table where to look for the column or alias + * @param string $table The table where to look for the column or alias * @param string $name The name or alias of the column to validate * @param RepositoryQuery $query An optional query to pass as context, * if not given no join will be attempted @@ -667,7 +619,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, public function requireQueryColumn($table, $name, RepositoryQuery $query = null) { if ($query === null || $this->validateQueryColumnAssociation($table, $name)) { - return parent::requireQueryColumn($this->removeTablePrefix($this->clearTableAlias($table)), $name, $query); + return parent::requireQueryColumn($table, $name, $query); } return $this->joinColumn($name, $table, $query); @@ -679,7 +631,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, * Attempts to join the given column from a different table if its association to the given table cannot be * verified. * - * @param array|string $table The table where to look for the column or alias + * @param string $table The table where to look for the column or alias * @param string $name The name or alias of the column to validate * @param RepositoryQuery $query An optional query to pass as context, * if not given the column is considered being used for a statement filter @@ -695,7 +647,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, } if ($this->validateQueryColumnAssociation($table, $name)) { - return parent::requireFilterColumn($this->removeTablePrefix($this->clearTableAlias($table)), $name, $query); + return parent::requireFilterColumn($table, $name, $query); } return $this->joinColumn($name, $table, $query); @@ -704,8 +656,8 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, /** * Return the statement column name for the given alias or null in case the alias does not exist * - * @param array|string $table - * @param string $alias + * @param string $table + * @param string $alias * * @return string|null */ @@ -716,7 +668,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, return $statementAliasColumnMap[$alias]; } - $prefixedAlias = $this->removeTablePrefix($this->clearTableAlias($table)) . '.' . $alias; + $prefixedAlias = $table . '.' . $alias; if (isset($statementAliasColumnMap[$prefixedAlias])) { return $statementAliasColumnMap[$prefixedAlias]; } @@ -725,8 +677,8 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, /** * Return the alias for the given statement column name or null in case the statement column does not exist * - * @param array|string $table - * @param string $column + * @param string $table + * @param string $column * * @return string|null */ @@ -737,7 +689,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, return $statementColumnAliasMap[$column]; } - $prefixedColumn = $this->removeTablePrefix($this->clearTableAlias($table)) . '.' . $column; + $prefixedColumn = $table . '.' . $column; if (isset($statementColumnAliasMap[$prefixedColumn])) { return $statementColumnAliasMap[$prefixedColumn]; } @@ -746,34 +698,32 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, /** * Return whether the given alias or statement column name is available in the given table * - * @param array|string $table - * @param string $alias + * @param string $table + * @param string $alias * * @return bool */ public function validateStatementColumnAssociation($table, $alias) { - $tableName = $this->removeTablePrefix($this->clearTableAlias($table)); - $statementAliasTableMap = $this->getStatementAliasTableMap(); if (isset($statementAliasTableMap[$alias])) { - return $statementAliasTableMap[$alias] === $tableName; + return $statementAliasTableMap[$alias] === $table; } $statementColumnTableMap = $this->getStatementColumnTableMap(); if (isset($statementColumnTableMap[$alias])) { - return $statementColumnTableMap[$alias] === $tableName; + return $statementColumnTableMap[$alias] === $table; } - $prefixedAlias = $tableName . '.' . $alias; + $prefixedAlias = $table . '.' . $alias; return isset($statementAliasTableMap[$prefixedAlias]) || isset($statementColumnTableMap[$prefixedAlias]); } /** * Return whether the given column name or alias of the given table is a valid statement column * - * @param array|string $table The table where to look for the column or alias - * @param string $name The column name or alias to check + * @param string $table The table where to look for the column or alias + * @param string $name The column name or alias to check * * @return bool */ @@ -784,7 +734,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, && $this->reassembleStatementColumnAlias($table, $name) === null) || !$this->validateStatementColumnAssociation($table, $name) ) { - return parent::hasStatementColumn($this->removeTablePrefix($this->clearTableAlias($table)), $name); + return parent::hasStatementColumn($table, $name); } return true; @@ -793,8 +743,8 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, /** * Validate that the given column is a valid statement column and return it or the actual name if it's an alias * - * @param array|string $table The table for which to require the column - * @param string $name The name or alias of the column to validate + * @param string $table The table for which to require the column + * @param string $name The name or alias of the column to validate * * @return string The given column's name * @@ -807,15 +757,11 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, } elseif (($alias = $this->reassembleStatementColumnAlias($table, $name)) !== null) { $column = $name; } else { - return parent::requireStatementColumn($this->removeTablePrefix($this->clearTableAlias($table)), $name); + return parent::requireStatementColumn($table, $name); } if (! $this->validateStatementColumnAssociation($table, $alias)) { - throw new StatementException( - 'Statement column "%s" not found in table "%s"', - $name, - $this->removeTablePrefix($this->clearTableAlias($table)) - ); + throw new StatementException('Statement column "%s" not found in table "%s"', $name, $table); } return $column; @@ -829,7 +775,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, * The method is called with the same parameters but in reversed order. * * @param string $name The alias or column name to join into $target - * @param array|string $target The table to join $name into + * @param string $target The table to join $name into * @param RepositoryQUery $query The query to apply the JOIN-clause on * * @return string The resolved alias or $name @@ -843,7 +789,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, throw new ProgrammingError( 'Unable to find a valid table for column "%s" to join into "%s"', $name, - $this->removeTablePrefix($this->clearTableAlias($target)) + $target ); } @@ -861,7 +807,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, throw new ProgrammingError( 'Unable to join table "%s" into "%s". Method "%s" not found', $tableName, - $this->removeTablePrefix($this->clearTableAlias($target)), + $target, $joinMethod ); } diff --git a/library/Icinga/Repository/RepositoryQuery.php b/library/Icinga/Repository/RepositoryQuery.php index 927f4f02b..8beba988b 100644 --- a/library/Icinga/Repository/RepositoryQuery.php +++ b/library/Icinga/Repository/RepositoryQuery.php @@ -79,8 +79,9 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera */ public function from($target, array $columns = null) { - $target = $this->repository->requireTable($target, $this); - $this->query = $this->repository->getDataSource()->select()->from($target); + $this->query = $this->repository->getDataSource()->select()->from( + $this->repository->requireTable($target, $this) + ); $this->query->columns($this->prepareQueryColumns($target, $columns)); $this->target = $target; return $this; From df7a2ee0a91190e64933c961fdd024e253df861b Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 30 Oct 2015 15:34:19 +0100 Subject: [PATCH 32/84] Repository: Add native support for virtual table names --- library/Icinga/Repository/Repository.php | 43 +++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Repository/Repository.php b/library/Icinga/Repository/Repository.php index 40ace5c01..091b286bb 100644 --- a/library/Icinga/Repository/Repository.php +++ b/library/Icinga/Repository/Repository.php @@ -52,6 +52,16 @@ abstract class Repository implements Selectable */ protected $baseTable; + /** + * The virtual tables being provided + * + * This may be initialized by concrete repository implementations with an array + * where a key is the name of a virtual table and its value the real table name. + * + * @var array + */ + protected $virtualTables; + /** * The query columns being provided * @@ -255,6 +265,32 @@ abstract class Repository implements Selectable return $this->baseTable; } + /** + * Return the virtual tables being provided + * + * Calls $this->initializeVirtualTables() in case $this->virtualTables is null. + * + * @return array + */ + public function getVirtualTables() + { + if ($this->virtualTables === null) { + $this->virtualTables = $this->initializeVirtualTables(); + } + + return $this->virtualTables; + } + + /** + * Overwrite this in your repository implementation in case you need to initialize the virtual tables lazily + * + * @return array + */ + protected function initializeVirtualTables() + { + return array(); + } + /** * Return the query columns being provided * @@ -792,7 +828,7 @@ abstract class Repository implements Selectable } /** - * Validate that the requested table exists + * Validate that the requested table exists and resolve it's real name if necessary * * @param string $table The table to validate * @param RepositoryQuery $query An optional query to pass as context @@ -809,6 +845,11 @@ abstract class Repository implements Selectable throw new ProgrammingError('Table "%s" not found', $table); } + $virtualTables = $this->getVirtualTables(); + if (isset($virtualTables[$table])) { + $table = $virtualTables[$table]; + } + return $table; } From a60ad3ecf07b17f0fc545ad20bc07fa630814017 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 30 Oct 2015 15:34:57 +0100 Subject: [PATCH 33/84] DbRepository: Return the number of affected rows for cud operations --- library/Icinga/Repository/DbRepository.php | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/library/Icinga/Repository/DbRepository.php b/library/Icinga/Repository/DbRepository.php index 27144d097..09024d1ee 100644 --- a/library/Icinga/Repository/DbRepository.php +++ b/library/Icinga/Repository/DbRepository.php @@ -294,11 +294,16 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, * * @param string $table * @param array $bind + * + * @return int The number of affected rows */ public function insert($table, array $bind) { $this->requireTable($table); - $this->ds->insert($this->prependTablePrefix($table), $this->requireStatementColumns($table, $bind)); + return $this->ds->insert( + $this->prependTablePrefix($table), + $this->requireStatementColumns($table, $bind) + ); } /** @@ -307,6 +312,8 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, * @param string $table * @param array $bind * @param Filter $filter + * + * @return int The number of affected rows */ public function update($table, array $bind, Filter $filter = null) { @@ -316,7 +323,11 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, $filter = $this->requireFilter($table, $filter); } - $this->ds->update($this->prependTablePrefix($table), $this->requireStatementColumns($table, $bind), $filter); + return $this->ds->update( + $this->prependTablePrefix($table), + $this->requireStatementColumns($table, $bind), + $filter + ); } /** @@ -324,6 +335,8 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, * * @param string $table * @param Filter $filter + * + * @return int The number of affected rows */ public function delete($table, Filter $filter = null) { @@ -333,7 +346,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, $filter = $this->requireFilter($table, $filter); } - $this->ds->delete($this->prependTablePrefix($table), $filter); + return $this->ds->delete($this->prependTablePrefix($table), $filter); } /** From 5dfaa8944020764b7339535da4a69be389b3459f Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 30 Oct 2015 15:35:41 +0100 Subject: [PATCH 34/84] Repository: Pass some more details to conversion methods --- library/Icinga/Repository/Repository.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/Icinga/Repository/Repository.php b/library/Icinga/Repository/Repository.php index 091b286bb..fe2b724b6 100644 --- a/library/Icinga/Repository/Repository.php +++ b/library/Icinga/Repository/Repository.php @@ -617,7 +617,7 @@ abstract class Repository implements Selectable { $converter = $this->getConverter($table, $name, 'persist', $query); if ($converter !== null) { - $value = $this->$converter($value); + $value = $this->$converter($value, $name, $table, $query); } return $value; @@ -639,7 +639,7 @@ abstract class Repository implements Selectable { $converter = $this->getConverter($table, $name, 'retrieve', $query); if ($converter !== null) { - $value = $this->$converter($value); + $value = $this->$converter($value, $name, $table, $query); } return $value; From 5db8d08729b8637862da27ffa23ece0868432486 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Fri, 30 Oct 2015 15:36:48 +0100 Subject: [PATCH 35/84] Repository: Fix that column normalisation is not applied by requireAllQueryColumns() --- library/Icinga/Repository/Repository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Repository/Repository.php b/library/Icinga/Repository/Repository.php index fe2b724b6..1f9854c6f 100644 --- a/library/Icinga/Repository/Repository.php +++ b/library/Icinga/Repository/Repository.php @@ -903,7 +903,7 @@ abstract class Repository implements Selectable $columns = array(); foreach ($queryColumns[$table] as $alias => $column) { if (! in_array(is_string($alias) ? $alias : $column, $blacklist)) { - $columns[$alias] = $column; + $columns[$alias] = $this->resolveQueryColumnAlias($table, $alias); } } From 71c5fd0bf7a2131db19bc9c18060967d2a2800f3 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 2 Nov 2015 17:07:02 +0100 Subject: [PATCH 36/84] DbRepository: Take virtual columns into consideration when applying aliases --- library/Icinga/Repository/DbRepository.php | 25 ++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/library/Icinga/Repository/DbRepository.php b/library/Icinga/Repository/DbRepository.php index 09024d1ee..5401f0d05 100644 --- a/library/Icinga/Repository/DbRepository.php +++ b/library/Icinga/Repository/DbRepository.php @@ -254,17 +254,24 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, * Return the given table with its alias being applied * * @param array|string $table + * @param string $virtualTable * * @return array|string */ - protected function applyTableAlias($table) + protected function applyTableAlias($table, $virtualTable = null) { $tableAliases = $this->getTableAliases(); - if (is_array($table) || !isset($tableAliases[($nonPrefixedTable = $this->removeTablePrefix($table))])) { - return $table; + if (! is_array($table)) { + if ($virtualTable !== null && isset($tableAliases[$virtualTable])) { + return array($tableAliases[$virtualTable] => $table); + } + + if (isset($tableAliases[($nonPrefixedTable = $this->removeTablePrefix($table))])) { + return array($tableAliases[$nonPrefixedTable] => $table); + } } - return array($tableAliases[$nonPrefixedTable] => $table); + return $table; } /** @@ -555,12 +562,18 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, */ public function requireTable($table, RepositoryQuery $query = null) { + $virtualTable = null; $statementColumns = $this->getStatementColumns(); if (! isset($statementColumns[$table])) { - $table = parent::requireTable($table); + $newTable = parent::requireTable($table); + if ($newTable !== $table) { + $virtualTable = $table; + } + + $table = $newTable; } - return $this->prependTablePrefix($this->applyTableAlias($table)); + return $this->prependTablePrefix($this->applyTableAlias($table, $virtualTable)); } /** From e170d5b79a70d47c3ff654d2a73c161c03c8af56 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 3 Nov 2015 10:45:39 +0100 Subject: [PATCH 37/84] Command::fail(): construct IcingaException printf-like The first argument of IcingaException::__construct() must be a printf-like format string, but Command::fail() must accept any message string. --- library/Icinga/Cli/Command.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Cli/Command.php b/library/Icinga/Cli/Command.php index 340c28945..07a939b94 100644 --- a/library/Icinga/Cli/Command.php +++ b/library/Icinga/Cli/Command.php @@ -127,7 +127,7 @@ abstract class Command public function fail($msg) { - throw new IcingaException($msg); + throw new IcingaException('%s', $msg); } public function getDefaultActionName() From 477af43a2fd3934af6c8e633ce6c7e4f76faeed7 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 3 Nov 2015 14:35:06 +0100 Subject: [PATCH 38/84] RepositoryQuery: Properly handle custom aliases when applying value conversion rules --- library/Icinga/Repository/RepositoryQuery.php | 73 ++++++++++++++++--- 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/library/Icinga/Repository/RepositoryQuery.php b/library/Icinga/Repository/RepositoryQuery.php index 8beba988b..2d36728f9 100644 --- a/library/Icinga/Repository/RepositoryQuery.php +++ b/library/Icinga/Repository/RepositoryQuery.php @@ -47,6 +47,13 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera */ protected $iterator; + /** + * This query's custom aliases + * + * @var array + */ + protected $customAliases; + /** * Create a new repository query * @@ -124,6 +131,7 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera */ protected function prepareQueryColumns($target, array $desiredColumns = null) { + $this->customAliases = array(); if (empty($desiredColumns)) { $columns = $this->repository->requireAllQueryColumns($target); } else { @@ -131,9 +139,15 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera foreach ($desiredColumns as $customAlias => $columnAlias) { $resolvedColumn = $this->repository->requireQueryColumn($target, $columnAlias, $this); if ($resolvedColumn !== $columnAlias) { - $columns[is_string($customAlias) ? $customAlias : $columnAlias] = $resolvedColumn; + if (is_string($customAlias)) { + $columns[$customAlias] = $resolvedColumn; + $this->customAliases[$customAlias] = $columnAlias; + } else { + $columns[$columnAlias] = $resolvedColumn; + } } elseif (is_string($customAlias)) { $columns[$customAlias] = $columnAlias; + $this->customAliases[$customAlias] = $columnAlias; } else { $columns[] = $columnAlias; } @@ -143,6 +157,24 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera return $columns; } + /** + * Return the native column alias for the given custom alias + * + * If no custom alias is found with the given name, it is returned unchanged. + * + * @param string $customAlias + * + * @return string + */ + protected function getNativeAlias($customAlias) + { + if (isset($this->customAliases[$customAlias])) { + return $this->customAliases[$customAlias]; + } + + return $customAlias; + } + /** * Return this query's available filter columns with their optional label as key * @@ -467,7 +499,7 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera $result = $this->query->fetchOne(); if ($result !== false && $this->repository->providesValueConversion($this->target)) { $columns = $this->getColumns(); - $column = isset($columns[0]) ? $columns[0] : key($columns); + $column = isset($columns[0]) ? $columns[0] : $this->getNativeAlias(key($columns)); return $this->repository->retrieveColumn($this->target, $column, $result, $this); } @@ -492,7 +524,12 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera $alias = $column; } - $result->$alias = $this->repository->retrieveColumn($this->target, $alias, $result->$alias, $this); + $result->$alias = $this->repository->retrieveColumn( + $this->target, + $this->getNativeAlias($alias), + $result->$alias, + $this + ); } } @@ -514,7 +551,7 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera if (! empty($results) && $this->repository->providesValueConversion($this->target)) { $columns = $this->getColumns(); $aliases = array_keys($columns); - $column = is_int($aliases[0]) ? $columns[0] : $aliases[0]; + $column = is_int($aliases[0]) ? $columns[0] : $this->getNativeAlias($aliases[0]); if ($this->repository->providesValueConversion($this->target, $column)) { foreach ($results as & $value) { $value = $this->repository->retrieveColumn($this->target, $column, $value, $this); @@ -542,8 +579,11 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera if (! empty($results) && $this->repository->providesValueConversion($this->target)) { $columns = $this->getColumns(); $aliases = array_keys($columns); - $colOne = $aliases[0] !== 0 ? $aliases[0] : $columns[0]; - $colTwo = count($aliases) < 2 ? $colOne : ($aliases[1] !== 1 ? $aliases[1] : $columns[1]); + $colOne = $aliases[0] !== 0 ? $this->getNativeAlias($aliases[0]) : $columns[0]; + $colTwo = count($aliases) < 2 ? $colOne : ( + $aliases[1] !== 1 ? $this->getNativeAlias($aliases[1]) : $columns[1] + ); + if ( $this->repository->providesValueConversion($this->target, $colOne) || $this->repository->providesValueConversion($this->target, $colTwo) @@ -588,21 +628,27 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera $alias = $column; } - $row->$alias = $this->repository->retrieveColumn($this->target, $alias, $row->$alias, $this); + $row->$alias = $this->repository->retrieveColumn( + $this->target, + $this->getNativeAlias($alias), + $row->$alias, + $this + ); } foreach (($this->getOrder() ?: array()) as $rule) { + $nativeAlias = $this->getNativeAlias($rule[0]); if (! array_key_exists($rule[0], $flippedColumns) && property_exists($row, $rule[0])) { - if ($this->repository->providesValueConversion($this->target, $rule[0])) { + if ($this->repository->providesValueConversion($this->target, $nativeAlias)) { $updateOrder = true; $row->{$rule[0]} = $this->repository->retrieveColumn( $this->target, - $rule[0], + $nativeAlias, $row->{$rule[0]} ); } } elseif (array_key_exists($rule[0], $flippedColumns)) { - if ($this->repository->providesValueConversion($this->target, $rule[0])) { + if ($this->repository->providesValueConversion($this->target, $nativeAlias)) { $updateOrder = true; } } @@ -678,7 +724,12 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera $alias = $column; } - $row->$alias = $this->repository->retrieveColumn($this->target, $alias, $row->$alias, $this); + $row->$alias = $this->repository->retrieveColumn( + $this->target, + $this->getNativeAlias($alias), + $row->$alias, + $this + ); } } From 48e6bdd6ce4b8c8dfb7e9f9ba83d085f70b469c3 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 3 Nov 2015 14:36:42 +0100 Subject: [PATCH 39/84] RepositoryQuery: Fix that the query is not passed as context in fetchAll() --- library/Icinga/Repository/RepositoryQuery.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Repository/RepositoryQuery.php b/library/Icinga/Repository/RepositoryQuery.php index 2d36728f9..532cae7c6 100644 --- a/library/Icinga/Repository/RepositoryQuery.php +++ b/library/Icinga/Repository/RepositoryQuery.php @@ -644,7 +644,8 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera $row->{$rule[0]} = $this->repository->retrieveColumn( $this->target, $nativeAlias, - $row->{$rule[0]} + $row->{$rule[0]}, + $this ); } } elseif (array_key_exists($rule[0], $flippedColumns)) { From 327cf373263bc15e7ea36679a94f6ce9e93fea1a Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 3 Nov 2015 16:11:20 +0100 Subject: [PATCH 40/84] Command: make $config and $configs protected --- library/Icinga/Cli/Command.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/Icinga/Cli/Command.php b/library/Icinga/Cli/Command.php index 07a939b94..d0c950a4e 100644 --- a/library/Icinga/Cli/Command.php +++ b/library/Icinga/Cli/Command.php @@ -28,9 +28,9 @@ abstract class Command protected $commandName; protected $actionName; - private $config; + protected $config; - private $configs; + protected $configs; protected $defaultActionName = 'default'; From dd069288e93083113777b1583be0ba7da7460500 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 4 Nov 2015 15:59:15 +0100 Subject: [PATCH 41/84] DbRepository: Do not attempt to join virtual tables multiple times --- library/Icinga/Repository/DbRepository.php | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/library/Icinga/Repository/DbRepository.php b/library/Icinga/Repository/DbRepository.php index 5401f0d05..9292a208b 100644 --- a/library/Icinga/Repository/DbRepository.php +++ b/library/Icinga/Repository/DbRepository.php @@ -576,6 +576,21 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, return $this->prependTablePrefix($this->applyTableAlias($table, $virtualTable)); } + /** + * Return the alias for the given table or null if none has been defined + * + * @param string $table + * + * @return string|null + */ + public function resolveTableAlias($table) + { + $tableAliases = $this->getTableAliases(); + if (isset($tableAliases[$table])) { + return $tableAliases[$table]; + } + } + /** * Recurse the given filter, require each column for the given table and convert all values * @@ -828,8 +843,10 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, $column = $name; } - $prefixedTableName = $this->prependTablePrefix($tableName); - if ($query->getQuery()->hasJoinedTable($prefixedTableName)) { + if (($joinIdentifier = $this->resolveTableAlias($tableName)) === null) { + $joinIdentifier = $this->prependTablePrefix($tableName); + } + if ($query->getQuery()->hasJoinedTable($joinIdentifier)) { return $column; } From ff61b8e696b16bf11a613130b430718ca52d0b09 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 5 Nov 2015 14:07:54 +0100 Subject: [PATCH 42/84] Repository: Fix that conversion rules for statements are applied on queries.. ..when calling RepositoryQuery::addFilter(). --- library/Icinga/Repository/Repository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Repository/Repository.php b/library/Icinga/Repository/Repository.php index 1f9854c6f..680b36def 100644 --- a/library/Icinga/Repository/Repository.php +++ b/library/Icinga/Repository/Repository.php @@ -873,7 +873,7 @@ abstract class Repository implements Selectable if ($filter->isExpression()) { $column = $filter->getColumn(); $filter->setColumn($this->requireFilterColumn($table, $column, $query)); - $filter->setExpression($this->persistColumn($table, $column, $filter->getExpression())); + $filter->setExpression($this->persistColumn($table, $column, $filter->getExpression(), $query)); } elseif ($filter->isChain()) { foreach ($filter->filters() as $chainOrExpression) { $this->requireFilter($table, $chainOrExpression, $query, false); From 1e35a17ec124cb771e22c0a2fcfb2fd5ed3b8e4e Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 5 Nov 2015 15:49:15 +0100 Subject: [PATCH 43/84] DbQuery: Support not equal comparisons with arrays --- library/Icinga/Data/Db/DbQuery.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/library/Icinga/Data/Db/DbQuery.php b/library/Icinga/Data/Db/DbQuery.php index 28c55efe8..bb93fe127 100644 --- a/library/Icinga/Data/Db/DbQuery.php +++ b/library/Icinga/Data/Db/DbQuery.php @@ -288,9 +288,14 @@ class DbQuery extends SimpleQuery $expression = $this->valueToTimestamp($expression); } - if (is_array($expression) && $sign === '=') { - // TODO: Should we support this? Doesn't work for blub* - return $col . ' IN (' . $this->escapeForSql($expression) . ')'; + if (is_array($expression)) { + if ($sign === '=') { + return $col . ' IN (' . $this->escapeForSql($expression) . ')'; + } elseif ($sign === '!=') { + return $col . ' NOT IN (' . $this->escapeForSql($expression) . ')'; + } + + throw new QueryException('Unable to render array expressions with operators other than equal or not equal'); } elseif ($sign === '=' && strpos($expression, '*') !== false) { if ($expression === '*') { // We'll ignore such filters as it prevents index usage and because "*" means anything, anything means From b5c8579d0e1739a9a8cecda937bf8cde09030de8 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Tue, 3 Nov 2015 17:16:00 +0100 Subject: [PATCH 44/84] lib: Fix Url paramter encoding if parameter is a Url object fixes #10321 --- library/Icinga/Web/UrlParams.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Web/UrlParams.php b/library/Icinga/Web/UrlParams.php index 504c076be..bd9022a7b 100644 --- a/library/Icinga/Web/UrlParams.php +++ b/library/Icinga/Web/UrlParams.php @@ -171,7 +171,7 @@ class UrlParams protected function urlEncode($value) { - return rawurlencode((string) $value); + return rawurlencode($value instanceof Url ? $value->getAbsoluteUrl() : (string) $value); } /** From ba55ad7c5a94d77845a216929f8ca3d7aa352dc7 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Tue, 3 Nov 2015 17:17:27 +0100 Subject: [PATCH 45/84] monitoring: Fix unhandled service counter in the hosts overview Group by was wrong. fixes #10490 --- .../Backend/Ido/Query/HostserviceproblemsummaryQuery.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostserviceproblemsummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostserviceproblemsummaryQuery.php index 794d51342..ff9d27fb3 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostserviceproblemsummaryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostserviceproblemsummaryQuery.php @@ -75,7 +75,7 @@ class HostserviceproblemsummaryQuery extends IdoQuery 's.service_object_id = so.object_id AND so.is_active = 1', array() ); - $this->select->group(array('so.object_id')); + $this->select->group(array('s.host_object_id')); $this->joinedVirtualTables['services'] = true; } From c14d33d1e8149d2c726126699db2ca36e88e6c8e Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Tue, 3 Nov 2015 17:19:19 +0100 Subject: [PATCH 46/84] Revert "doc: Fix upgrade documentation for 2.0.0" This reverts commit 20e010bea07df8a37585c9459d999671068d1fbd. There's a pending PR on GitHub. --- doc/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/installation.md b/doc/installation.md index 56bf92732..cf1d444aa 100644 --- a/doc/installation.md +++ b/doc/installation.md @@ -495,6 +495,6 @@ The first release candidate of Icinga Web 2 introduces the following non-backwar the file remains unchanged. * The location of a user's preferences has been changed from - **config-dir/preferences/username.ini** to - **config-dir/preferences/username/config.ini**. + **/preferences/.ini** to + **/preferences//config.ini**. The content of the file remains unchanged. From 7b37a0872e5a0b14afbbe19c9edb96f82e6e2571 Mon Sep 17 00:00:00 2001 From: Benedikt Heine Date: Fri, 9 Oct 2015 11:50:05 +0200 Subject: [PATCH 47/84] Escape < and > to avoid false tags Signed-off-by: Eric Lippmann --- doc/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/installation.md b/doc/installation.md index cf1d444aa..156c26af7 100644 --- a/doc/installation.md +++ b/doc/installation.md @@ -495,6 +495,6 @@ The first release candidate of Icinga Web 2 introduces the following non-backwar the file remains unchanged. * The location of a user's preferences has been changed from - **/preferences/.ini** to - **/preferences//config.ini**. + **<config-dir>/preferences/<username>.ini** to + **<config-dir>/preferences/<username>/config.ini**. The content of the file remains unchanged. From 2e650f6aa47ea2b510c4afd2c1c9fbdf7bda5d62 Mon Sep 17 00:00:00 2001 From: mbaschnitzi Date: Fri, 2 Oct 2015 16:28:04 +0200 Subject: [PATCH 48/84] Rename moduel.info to module.info Fix a typo in filename of iframe module info file. Signed-off-by: Eric Lippmann --- modules/iframe/{moduel.info => module.info} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/iframe/{moduel.info => module.info} (100%) diff --git a/modules/iframe/moduel.info b/modules/iframe/module.info similarity index 100% rename from modules/iframe/moduel.info rename to modules/iframe/module.info From 62eb767cd5698bdf23fac9e04f2a635fbfd44ec6 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Mon, 9 Nov 2015 10:45:26 +0100 Subject: [PATCH 49/84] RPM: Ship Zend Framework as vendor libraries for OpenSUSE and SLES Afaik, there's no change to distinct OpenSUSE and SLES in spec files. OpenSUSE has Zend Framework packages through the server:monitoring repository available but SLES not. Thus we ship Zend Framework as vendor libraries for both for now. fixes #10328 --- icingaweb2.spec | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/icingaweb2.spec b/icingaweb2.spec index 6fc564666..daa49180d 100644 --- a/icingaweb2.spec +++ b/icingaweb2.spec @@ -25,7 +25,7 @@ Packager: Icinga Team %if 0%{?suse_version} %define wwwconfigdir %{_sysconfdir}/apache2/conf.d %define wwwuser wwwrun -%define zend php5-ZendFramework +%define zend %{name}-vendor-Zend %if 0%{?suse_version} == 1110 %define php php53 Requires: apache2-mod_php53 @@ -83,7 +83,6 @@ Requires: %{php}-gd %{php}-intl %{?rhel:Requires: php-pecl-imagick} %{?suse_version:Requires: %{php}-gettext %{php}-json %{php}-openssl %{php}-posix} Requires: %{zend} -Obsoletes: %{name}-vendor-zend Requires: %{zend}-Db-Adapter-Pdo-Mysql Requires: %{zend}-Db-Adapter-Pdo-Pgsql From 6551a86d4d82153bb4831c2f790a0c5c828f88e0 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 9 Nov 2015 11:40:30 +0100 Subject: [PATCH 50/84] LdapRepository: Drop method isAmbiguous() and introduce isRelatedDn() refs #10567 --- library/Icinga/Repository/LdapRepository.php | 23 ++++++++------------ 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/library/Icinga/Repository/LdapRepository.php b/library/Icinga/Repository/LdapRepository.php index 5e79c41bb..0ebde56dd 100644 --- a/library/Icinga/Repository/LdapRepository.php +++ b/library/Icinga/Repository/LdapRepository.php @@ -42,15 +42,6 @@ abstract class LdapRepository extends Repository 'groupofuniquenames' => 'groupOfUniqueNames' ); - /** - * Object attributes whose value is not distinguished name - * - * @var array - */ - protected $ambiguousAttributes = array( - 'posixGroup' => 'memberUid' - ); - /** * Create a new LDAP repository object * @@ -79,15 +70,19 @@ abstract class LdapRepository extends Repository } /** - * Return whether the given object attribute's value is not a distinguished name + * Return whether the given object DN is related to the given base DN * - * @param string $objectClass - * @param string $attributeName + * Will use the current connection's root DN if $baseDn is not given. + * + * @param string $dn The object DN to check + * @param string $baseDn The base DN to compare the object DN with * * @return bool */ - protected function isAmbiguous($objectClass, $attributeName) + protected function isRelatedDn($dn, $baseDn = null) { - return isset($this->ambiguousAttributes[$objectClass][$attributeName]); + $normalizedDn = strtolower(join(',', array_map('trim', explode(',', $dn)))); + $normalizedBaseDn = strtolower(join(',', array_map('trim', explode(',', $baseDn ?: $this->ds->getDn())))); + return strpos($normalizedDn, $normalizedBaseDn) !== false; } } From cfb26e22b35c8e19767233a4db3a22864453a483 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 9 Nov 2015 11:41:11 +0100 Subject: [PATCH 51/84] LdapUserGroupBackend: Dynamically verify member attribute ambiguity refs #10567 --- .../UserGroup/LdapUserGroupBackend.php | 54 ++++++++++++++----- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index 9a4ab53c0..8c3fc3c3d 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -72,6 +72,13 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt */ protected $groupMemberAttribute; + /** + * Whether the attribute name where to find a group's member holds ambiguous values + * + * @var bool + */ + protected $ambiguousMemberAttribute; + /** * The custom LDAP filter to apply on a user query * @@ -357,6 +364,39 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt return $this->groupFilter; } + /** + * Return whether the attribute name where to find a group's member holds ambiguous values + * + * @return bool + * + * @throws ProgrammingError In case either $this->groupClass or $this->groupMemberAttribute + * has not been set yet + */ + protected function isMemberAttributeAmbiguous() + { + if ($this->ambiguousMemberAttribute === null) { + if ($this->groupClass === null) { + throw new ProgrammingError( + 'It is required to set the objectClass where to look for groups first' + ); + } elseif ($this->groupMemberAttribute === null) { + throw new ProgrammingError( + 'It is required to set a attribute name where to find a group\'s members first' + ); + } + + $sampleValue = $this->ds + ->select() + ->from($this->groupClass, array($this->groupMemberAttribute)) + ->setUnfoldAttribute($this->groupMemberAttribute) + ->setBase($this->groupBaseDn) + ->fetchOne(); + $this->ambiguousMemberAttribute = !$this->isRelatedDn($sampleValue); + } + + return $this->ambiguousMemberAttribute; + } + /** * Return a new query for the given columns * @@ -431,19 +471,9 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt * Initialize this repository's conversion rules * * @return array - * - * @throws ProgrammingError In case either $this->groupClass or $this->groupMemberAttribute - * 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'); - } - if ($this->groupMemberAttribute === null) { - throw new ProgrammingError('It is required to set a attribute name where to find a group\'s members first'); - } - $rules = array( 'group' => array( 'created_at' => 'generalized_time', @@ -454,7 +484,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt 'last_modified' => 'generalized_time' ) ); - if (! $this->isAmbiguous($this->groupClass, $this->groupMemberAttribute)) { + if (! $this->isMemberAttributeAmbiguous()) { $rules['group_membership']['user_name'] = 'user_name'; $rules['group_membership']['user'] = 'user_name'; $rules['group']['user_name'] = 'user_name'; @@ -566,7 +596,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt */ public function getMemberships(User $user) { - if ($this->isAmbiguous($this->groupClass, $this->groupMemberAttribute)) { + if ($this->isMemberAttributeAmbiguous()) { $queryValue = $user->getUsername(); } elseif (($queryValue = $user->getAdditional('ldap_dn')) === null) { $userQuery = $this->ds From 9b826e6e5fbbd649bcec67a4f4ca153b09f98828 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 9 Nov 2015 13:04:02 +0100 Subject: [PATCH 52/84] Drop class Ldap\Expression and introduce LdapQuery::$nativeFilter I'm about to add support for our Data\Filter implementation, since it cannot parse native LDAP filters and a user may have configured such, we need to differentiate the two types of filter. refs #10370 --- .../Authentication/User/LdapUserBackend.php | 3 +- .../UserGroup/LdapUserGroupBackend.php | 17 +++-- library/Icinga/Protocol/Ldap/Expression.php | 30 --------- library/Icinga/Protocol/Ldap/LdapQuery.php | 62 +++++++++++++------ 4 files changed, 53 insertions(+), 59 deletions(-) delete mode 100644 library/Icinga/Protocol/Ldap/Expression.php diff --git a/library/Icinga/Authentication/User/LdapUserBackend.php b/library/Icinga/Authentication/User/LdapUserBackend.php index b5bd51f45..b26b8aad7 100644 --- a/library/Icinga/Authentication/User/LdapUserBackend.php +++ b/library/Icinga/Authentication/User/LdapUserBackend.php @@ -12,7 +12,6 @@ use Icinga\Exception\ProgrammingError; use Icinga\Repository\LdapRepository; use Icinga\Repository\RepositoryQuery; use Icinga\Protocol\Ldap\LdapException; -use Icinga\Protocol\Ldap\Expression; use Icinga\User; class LdapUserBackend extends LdapRepository implements UserBackendInterface, Inspectable @@ -203,7 +202,7 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In $query = parent::select($columns); $query->getQuery()->setBase($this->baseDn); if ($this->filter) { - $query->getQuery()->where(new Expression($this->filter)); + $query->getQuery()->setNativeFilter($this->filter); } return $query; diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index 9a4ab53c0..4c11d1a68 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -9,7 +9,6 @@ use Icinga\Application\Logger; use Icinga\Data\ConfigObject; use Icinga\Exception\ConfigurationError; use Icinga\Exception\ProgrammingError; -use Icinga\Protocol\Ldap\Expression; use Icinga\Repository\LdapRepository; use Icinga\Repository\RepositoryQuery; use Icinga\User; @@ -368,11 +367,6 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt { $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; } @@ -529,7 +523,12 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt public function requireTable($table, RepositoryQuery $query = null) { $table = parent::requireTable($table, $query); - if ($table === 'group' || $table === 'group_membership') { + if ($table === 'group') { + $table = $this->groupClass; + if ($query !== null && $this->groupFilter) { + $query->getQuery()->setNativeFilter($this->groupFilter); + } + } elseif ($table === 'group_memership') { $table = $this->groupClass; } @@ -576,7 +575,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt ->setBase($this->userBaseDn) ->setUsePagedResults(false); if ($this->userFilter) { - $userQuery->where(new Expression($this->userFilter)); + $userQuery->setNativeFilter($this->userFilter); } if (($queryValue = $userQuery->fetchDn()) === null) { @@ -590,7 +589,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt ->where($this->groupMemberAttribute, $queryValue) ->setBase($this->groupBaseDn); if ($this->groupFilter) { - $groupQuery->where(new Expression($this->groupFilter)); + $groupQuery->setNativeFilter($this->groupFilter); } $groups = array(); diff --git a/library/Icinga/Protocol/Ldap/Expression.php b/library/Icinga/Protocol/Ldap/Expression.php deleted file mode 100644 index 403e1fd04..000000000 --- a/library/Icinga/Protocol/Ldap/Expression.php +++ /dev/null @@ -1,30 +0,0 @@ -value = $value; - } - - public function setValue($value) - { - $this->value = $value; - return $this; - } - - public function getValue() - { - return $this->value; - } - - public function __toString() - { - return (string) $this->getValue(); - } -} diff --git a/library/Icinga/Protocol/Ldap/LdapQuery.php b/library/Icinga/Protocol/Ldap/LdapQuery.php index 2832908ea..3696062ef 100644 --- a/library/Icinga/Protocol/Ldap/LdapQuery.php +++ b/library/Icinga/Protocol/Ldap/LdapQuery.php @@ -42,6 +42,13 @@ class LdapQuery extends SimpleQuery */ protected $unfoldAttribute; + /** + * This query's native LDAP filter + * + * @var string + */ + protected $nativeFilter; + /** * Initialize this query */ @@ -120,6 +127,29 @@ class LdapQuery extends SimpleQuery return $this->unfoldAttribute; } + /** + * Set this query's native LDAP filter + * + * @param string $filter + * + * @return $this + */ + public function setNativeFilter($filter) + { + $this->nativeFilter = $filter; + return $this; + } + + /** + * Return this query's native LDAP filter + * + * @return string + */ + public function getNativeFilter() + { + return $this->nativeFilter; + } + /** * Choose an objectClass and the columns you are interested in * @@ -141,13 +171,7 @@ class LdapQuery extends SimpleQuery */ public function where($condition, $value = null) { - // TODO: Adjust this once support for Icinga\Data\Filter is available - if ($condition instanceof Expression) { - $this->filters[] = $condition; - } else { - $this->filters[$condition] = $value; - } - + $this->filters[$condition] = $value; return $this; } @@ -239,22 +263,24 @@ class LdapQuery extends SimpleQuery $parts = array(); foreach ($this->filters as $key => $value) { - if ($value instanceof Expression) { - $parts[] = (string) $value; - } else { - $parts[] = sprintf( - '%s=%s', - LdapUtils::quoteForSearch($key), - LdapUtils::quoteForSearch($value, true) - ); - } + $parts[] = sprintf( + '%s=%s', + LdapUtils::quoteForSearch($key), + LdapUtils::quoteForSearch($value, true) + ); } if (count($parts) > 1) { - return '(&(' . implode(')(', $parts) . '))'; + $filter = '(&(' . implode(')(', $parts) . '))'; } else { - return '(' . $parts[0] . ')'; + $filter = '(' . $parts[0] . ')'; } + + if ($this->nativeFilter) { + $filter = '(&(' . $this->nativeFilter . ')' . $filter . ')'; + } + + return $filter; } /** From 4341eef4b10a0a6101a1f87b70d5394b2197b5bb Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 9 Nov 2015 15:59:48 +0100 Subject: [PATCH 53/84] LdapQuery: Add support for Icinga\Data\Filter refs #10370 --- .../Icinga/Protocol/Ldap/LdapConnection.php | 92 ++++++++++++++++++- library/Icinga/Protocol/Ldap/LdapQuery.php | 77 +--------------- 2 files changed, 94 insertions(+), 75 deletions(-) diff --git a/library/Icinga/Protocol/Ldap/LdapConnection.php b/library/Icinga/Protocol/Ldap/LdapConnection.php index ceefc81b8..fe18c100e 100644 --- a/library/Icinga/Protocol/Ldap/LdapConnection.php +++ b/library/Icinga/Protocol/Ldap/LdapConnection.php @@ -7,13 +7,14 @@ use Exception; use ArrayIterator; use Icinga\Application\Config; use Icinga\Application\Logger; -use Icinga\Application\Platform; use Icinga\Data\ConfigObject; use Icinga\Data\Inspectable; use Icinga\Data\Inspection; use Icinga\Data\Selectable; use Icinga\Data\Sortable; -use Icinga\Exception\InspectionException; +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterChain; +use Icinga\Data\Filter\FilterExpression; use Icinga\Exception\ProgrammingError; use Icinga\Protocol\Ldap\LdapException; @@ -1184,6 +1185,93 @@ class LdapConnection implements Selectable, Inspectable return $dir; } + /** + * Render and return a valid LDAP filter representation of the given filter + * + * @param Filter $filter + * @param int $level + * + * @return string + */ + public function renderFilter(Filter $filter, $level = 0) + { + if ($filter->isExpression()) { + /** @var $filter FilterExpression */ + return $this->renderFilterExpression($filter); + } + + /** @var $filter FilterChain */ + $parts = array(); + foreach ($filter->filters() as $filterPart) { + $part = $this->renderFilter($filterPart, $level + 1); + if ($part) { + $parts[] = $part; + } + } + + if (empty($parts)) { + return ''; + } + + $format = '%1$s(%2$s)'; + if (count($parts) === 1) { + $format = '%2$s'; + } + if ($level === 0) { + $format = '(' . $format . ')'; + } + + return sprintf($format, $filter->getOperatorSymbol(), implode(')(', $parts)); + } + + /** + * Render and return a valid LDAP filter expression of the given filter + * + * @param FilterExpression $filter + * + * @return string + */ + protected function renderFilterExpression(FilterExpression $filter) + { + $column = $filter->getColumn(); + $sign = $filter->getSign(); + $expression = $filter->getExpression(); + $format = '%1$s%2$s%3$s'; + + if ($expression === null || $expression === true) { + $expression = '*'; + } elseif (is_array($expression)) { + $seqFormat = '|(%s)'; + if ($sign === '!=') { + $seqFormat = '!(' . $seqFormat . ')'; + $sign = '='; + } + + $seqParts = array(); + foreach ($expression as $expressionValue) { + $seqParts[] = sprintf( + $format, + LdapUtils::quoteForSearch($column), + $sign, + LdapUtils::quoteForSearch($expressionValue, true) + ); + } + + return sprintf($seqFormat, implode(')(', $seqParts)); + } + + if ($sign === '!=') { + $format = '!(%1$s=%3$s)'; + } + + return sprintf( + $format, + LdapUtils::quoteForSearch($column), + $sign, + LdapUtils::quoteForSearch($expression, true) + ); + } + /** * Inspect if this LDAP Connection is working as expected * diff --git a/library/Icinga/Protocol/Ldap/LdapQuery.php b/library/Icinga/Protocol/Ldap/LdapQuery.php index 3696062ef..bf37f72dd 100644 --- a/library/Icinga/Protocol/Ldap/LdapQuery.php +++ b/library/Icinga/Protocol/Ldap/LdapQuery.php @@ -4,23 +4,12 @@ namespace Icinga\Protocol\Ldap; use Icinga\Data\SimpleQuery; -use Icinga\Data\Filter\Filter; -use Icinga\Exception\NotImplementedError; /** * LDAP query class */ class LdapQuery extends SimpleQuery { - /** - * This query's filters - * - * Currently just a basic key/value pair based array. Can be removed once Icinga\Data\Filter is supported. - * - * @var array - */ - protected $filters; - /** * The base dn being used for this query * @@ -54,7 +43,6 @@ class LdapQuery extends SimpleQuery */ protected function init() { - $this->filters = array(); $this->usePagedResults = false; } @@ -157,47 +145,10 @@ class LdapQuery extends SimpleQuery */ public function from($target, array $fields = null) { - $this->filters['objectClass'] = $target; + $this->where('objectClass', $target); return parent::from($target, $fields); } - /** - * Add a new filter to the query - * - * @param string $condition Column to search in - * @param mixed $value Value to look for (asterisk wildcards are allowed) - * - * @return $this - */ - public function where($condition, $value = null) - { - $this->filters[$condition] = $value; - return $this; - } - - public function getFilter() - { - throw new NotImplementedError('Support for Icinga\Data\Filter is still missing. Use $this->where() instead'); - } - - public function addFilter(Filter $filter) - { - // TODO: This should be considered a quick fix only. - // Drop this entirely once support for Icinga\Data\Filter is available - if ($filter->isExpression()) { - $this->where($filter->getColumn(), $filter->getExpression()); - } elseif ($filter->isChain()) { - foreach ($filter->filters() as $chainOrExpression) { - $this->addFilter($chainOrExpression); - } - } - } - - public function setFilter(Filter $filter) - { - throw new NotImplementedError('Support for Icinga\Data\Filter is still missing. Use $this->where() instead'); - } - /** * Fetch result as tree * @@ -249,33 +200,13 @@ class LdapQuery extends SimpleQuery } /** - * Return the LDAP filter to be applied on this query + * Render and return this query's filter * * @return string - * - * @throws LdapException In case the objectClass filter does not exist */ - protected function renderFilter() + public function renderFilter() { - if (! isset($this->filters['objectClass'])) { - throw new LdapException('Object class is mandatory'); - } - - $parts = array(); - foreach ($this->filters as $key => $value) { - $parts[] = sprintf( - '%s=%s', - LdapUtils::quoteForSearch($key), - LdapUtils::quoteForSearch($value, true) - ); - } - - if (count($parts) > 1) { - $filter = '(&(' . implode(')(', $parts) . '))'; - } else { - $filter = '(' . $parts[0] . ')'; - } - + $filter = $this->ds->renderFilter($this->filter); if ($this->nativeFilter) { $filter = '(&(' . $this->nativeFilter . ')' . $filter . ')'; } From ffcc2ed56b868462fbc74a9b0ba8608ac0ac0c7e Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 9 Nov 2015 16:00:24 +0100 Subject: [PATCH 54/84] LdapUserGroupBackend: Fix exception when searching for single chars refs #10370 --- .../UserGroup/LdapUserGroupBackend.php | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index 4c11d1a68..9b7aaf691 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -9,6 +9,7 @@ use Icinga\Application\Logger; use Icinga\Data\ConfigObject; use Icinga\Exception\ConfigurationError; use Icinga\Exception\ProgrammingError; +use Icinga\Protocol\Ldap\LdapException; use Icinga\Repository\LdapRepository; use Icinga\Repository\RepositoryQuery; use Icinga\User; @@ -467,24 +468,28 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt */ protected function persistUserName($name) { - $userDn = $this->ds - ->select() - ->from($this->userClass, array()) - ->where($this->userNameAttribute, $name) - ->setUsePagedResults(false) - ->fetchDn(); - if ($userDn) { - return $userDn; - } + try { + $userDn = $this->ds + ->select() + ->from($this->userClass, array()) + ->where($this->userNameAttribute, $name) + ->setUsePagedResults(false) + ->fetchDn(); + if ($userDn) { + return $userDn; + } - $groupDn = $this->ds - ->select() - ->from($this->groupClass, array()) - ->where($this->groupNameAttribute, $name) - ->setUsePagedResults(false) - ->fetchDn(); - if ($groupDn) { - return $groupDn; + $groupDn = $this->ds + ->select() + ->from($this->groupClass, array()) + ->where($this->groupNameAttribute, $name) + ->setUsePagedResults(false) + ->fetchDn(); + if ($groupDn) { + return $groupDn; + } + } catch (LdapException $_) { + // pass } Logger::debug('Unable to persist uid or gid "%s" in repository "%s". No DN found.', $name, $this->getName()); From c416216822e5183f8279673d804e08e3c0fd601e Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 9 Nov 2015 16:00:55 +0100 Subject: [PATCH 55/84] LdapUserGroupBackend: Fix typo in method requireTable() refs #10370 --- .../Icinga/Authentication/UserGroup/LdapUserGroupBackend.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index 9b7aaf691..50365a3ef 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -533,7 +533,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt if ($query !== null && $this->groupFilter) { $query->getQuery()->setNativeFilter($this->groupFilter); } - } elseif ($table === 'group_memership') { + } elseif ($table === 'group_membership') { $table = $this->groupClass; } From 505f5902c7b70e3bd8ed464ef94f75e5af65f8e0 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 10 Nov 2015 09:56:27 +0100 Subject: [PATCH 56/84] LdapUserBackend: Utilize $virtualTables --- .../Authentication/User/LdapUserBackend.php | 41 ++++++++----------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/library/Icinga/Authentication/User/LdapUserBackend.php b/library/Icinga/Authentication/User/LdapUserBackend.php index b26b8aad7..930ac4c99 100644 --- a/library/Icinga/Authentication/User/LdapUserBackend.php +++ b/library/Icinga/Authentication/User/LdapUserBackend.php @@ -208,6 +208,24 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In return $query; } + /** + * Initialize this repository's virtual tables + * + * @return array + * + * @throws ProgrammingError In case $this->userClass has not been set yet + */ + protected function initializeVirtualTables() + { + if ($this->userClass === null) { + throw new ProgrammingError('It is required to set the object class where to find users first'); + } + + return array( + 'user' => $this->userClass + ); + } + /** * Initialize this repository's query columns * @@ -317,29 +335,6 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In return ((int) $value) >= $bigBang->diff($now)->days; } - /** - * Validate that the requested table exists - * - * This will return $this->userClass in case $table equals "user". - * - * @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 === 'user') { - $table = $this->userClass; - } - - return $table; - } - /** * Validate that the given column is a valid query target and return it or the actual name if it's an alias * From d56056bba79f9412a8d4c6f3e955a64546be5a88 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 10 Nov 2015 09:56:58 +0100 Subject: [PATCH 57/84] LdapUserGroupBackend: Utilize $virtualTables --- .../UserGroup/LdapUserGroupBackend.php | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index 50365a3ef..e0e957ee3 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -371,6 +371,25 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt return $query; } + /** + * Initialize this repository's virtual tables + * + * @return array + * + * @throws ProgrammingError In case $this->groupClass has not been set yet + */ + protected function initializeVirtualTables() + { + if ($this->groupClass === null) { + throw new ProgrammingError('It is required to set the object class where to find groups first'); + } + + return array( + 'group' => $this->groupClass, + 'group_membership' => $this->groupClass + ); + } + /** * Initialize this repository's query columns * @@ -515,11 +534,8 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt /** * 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 * @@ -527,17 +543,11 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt */ public function requireTable($table, RepositoryQuery $query = null) { - $table = parent::requireTable($table, $query); - if ($table === 'group') { - $table = $this->groupClass; - if ($query !== null && $this->groupFilter) { - $query->getQuery()->setNativeFilter($this->groupFilter); - } - } elseif ($table === 'group_membership') { - $table = $this->groupClass; + if ($query !== null && $table === 'group' && $this->groupFilter) { + $query->getQuery()->setNativeFilter($this->groupFilter); } - return $table; + return parent::requireTable($table, $query); } /** From 8d04c8548ad220a309dc5b04b1db278b16981b93 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 10 Nov 2015 11:51:26 +0100 Subject: [PATCH 58/84] Do not hardcode action specific parameters to preserve in the FilterEditor This should only happen for other control parameters or framework specific stuff. This is still subject to improvement, as this solution is rather ugly imho.. refs #10370 --- application/controllers/GroupController.php | 2 +- application/controllers/UserController.php | 3 +- library/Icinga/Web/Controller.php | 36 ++++++++++--------- .../controllers/ListController.php | 7 +++- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/application/controllers/GroupController.php b/application/controllers/GroupController.php index 21fba5ebb..949aa8fc6 100644 --- a/application/controllers/GroupController.php +++ b/application/controllers/GroupController.php @@ -97,7 +97,7 @@ class GroupController extends AuthBackendController ->from('group_membership', array('user_name')) ->where('group_name', $groupName); - $this->setupFilterControl($members, null, array('user')); + $this->setupFilterControl($members, null, array('user'), array('group')); $this->setupPaginationControl($members); $this->setupLimitControl(); $this->setupSortControl( diff --git a/application/controllers/UserController.php b/application/controllers/UserController.php index 69d4a71a5..573d85c93 100644 --- a/application/controllers/UserController.php +++ b/application/controllers/UserController.php @@ -99,7 +99,8 @@ class UserController extends AuthBackendController $this->setupFilterControl( $memberships, array('group_name' => t('User Group')), - array('group_name') + array('group_name'), + array('user') ); $this->setupPaginationControl($memberships); $this->setupLimitControl(); diff --git a/library/Icinga/Web/Controller.php b/library/Icinga/Web/Controller.php index ac92e45b6..891b70329 100644 --- a/library/Icinga/Web/Controller.php +++ b/library/Icinga/Web/Controller.php @@ -186,6 +186,7 @@ class Controller extends ModuleActionController * @param Filterable $filterable The filterable to create a filter editor for * @param array $filterColumns The filter columns to offer to the user * @param array $searchColumns The search columns to utilize for quick searches + * @param array $preserveParams The url parameters to preserve * * @return $this * @@ -194,25 +195,26 @@ class Controller extends ModuleActionController protected function setupFilterControl( Filterable $filterable, array $filterColumns = null, - array $searchColumns = null + array $searchColumns = null, + array $preserveParams = null ) { - $editor = Widget::create('filterEditor') + $defaultPreservedParams = array( + 'limit', // setupPaginationControl() + 'sort', // setupSortControl() + 'dir', // setupSortControl() + 'backend', // Framework + '_dev' // Framework + ); + + $editor = Widget::create('filterEditor'); + call_user_func_array( + array($editor, 'preserveParams'), + array_merge($defaultPreservedParams, $preserveParams ?: array()) + ); + + $editor ->setQuery($filterable) - ->preserveParams( - 'limit', - 'sort', - 'dir', - 'format', - 'view', - 'user', - 'group', - 'backend', - 'stateType', - 'addColumns', - 'problems', - '_dev' - ) - ->ignoreParams('page') + ->ignoreParams('page') // setupPaginationControl() ->setColumns($filterColumns) ->setSearchColumns($searchColumns) ->handleRequest($this->getRequest()); diff --git a/modules/monitoring/application/controllers/ListController.php b/modules/monitoring/application/controllers/ListController.php index d08257557..c0ae1133f 100644 --- a/modules/monitoring/application/controllers/ListController.php +++ b/modules/monitoring/application/controllers/ListController.php @@ -574,7 +574,12 @@ class ListController extends Controller */ protected function filterQuery(DataView $dataView) { - $this->setupFilterControl($dataView); + $this->setupFilterControl($dataView, null, null, array( + 'format', // handleFormatRequest() + 'stateType', // hostsAction() and servicesAction() + 'addColumns', // addColumns() + 'problems' // servicegridAction() + )); $this->handleFormatRequest($dataView); return $dataView; } From 72f3ba116191401195c5e355b111f3f31c687998 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 10 Nov 2015 11:52:06 +0100 Subject: [PATCH 59/84] LdapUserGroupBackend: Offer "user_name" as filter column instead of "user" refs #10370 --- .../Icinga/Authentication/UserGroup/LdapUserGroupBackend.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index e0e957ee3..fab3d6bea 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -434,7 +434,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt protected function initializeFilterColumns() { return array( - t('Username') => 'user', + t('Username') => 'user_name', t('User Group') => 'group_name', t('Created At') => 'created_at', t('Last Modified') => 'last_modified' From 7efefc1975564d606c01b1aa629abe4e794fdcab Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 10 Nov 2015 11:52:54 +0100 Subject: [PATCH 60/84] UserController: Use "group" instead of "group_name" for the membership quicksearch refs #10370 --- application/controllers/UserController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/controllers/UserController.php b/application/controllers/UserController.php index 573d85c93..ae7cf841a 100644 --- a/application/controllers/UserController.php +++ b/application/controllers/UserController.php @@ -99,7 +99,7 @@ class UserController extends AuthBackendController $this->setupFilterControl( $memberships, array('group_name' => t('User Group')), - array('group_name'), + array('group'), array('user') ); $this->setupPaginationControl($memberships); @@ -260,6 +260,7 @@ class UserController extends AuthBackendController $alreadySeen[$groupName] = null; $groups[] = (object) array( 'group_name' => $groupName, + 'group' => $groupName, 'backend' => $backend ); } From 6c07881466804bd9748eecdb6012187879e76e41 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 10 Nov 2015 13:16:40 +0100 Subject: [PATCH 61/84] FilterChain: Fix and document method listFilterColumns() refs #10370 --- library/Icinga/Data/Filter/FilterChain.php | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/library/Icinga/Data/Filter/FilterChain.php b/library/Icinga/Data/Filter/FilterChain.php index 51d511785..c6354e402 100644 --- a/library/Icinga/Data/Filter/FilterChain.php +++ b/library/Icinga/Data/Filter/FilterChain.php @@ -125,17 +125,26 @@ abstract class FilterChain extends Filter return $this; } - public function listFilteredColumns() + /** + * List and return all column names referenced in this filter + * + * @param array $columns The columns listed so far + * + * @return array + */ + public function listFilteredColumns(array $columns = array()) { - $columns = array(); foreach ($this->filters as $filter) { if ($filter instanceof FilterExpression) { - $columns[] = $filter->getColumn(); + $column= $filter->getColumn(); + if (! in_array($column, $columns, true)) { + $columns[] = $column; + } } else { - $columns += $filter->listFilteredColumns(); + $columns = $filter->listFilteredColumns($columns); } } - // array_unique? + return $columns; } From 666e67b405ff065f03148e48f00d4964b93c22a8 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 10 Nov 2015 13:17:30 +0100 Subject: [PATCH 62/84] LdapConnection: Prefer strict checks when utilizing in_array() --- library/Icinga/Protocol/Ldap/LdapConnection.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/library/Icinga/Protocol/Ldap/LdapConnection.php b/library/Icinga/Protocol/Ldap/LdapConnection.php index fe18c100e..95d21b987 100644 --- a/library/Icinga/Protocol/Ldap/LdapConnection.php +++ b/library/Icinga/Protocol/Ldap/LdapConnection.php @@ -695,7 +695,7 @@ class LdapConnection implements Selectable, Inspectable )); } else { foreach ($query->getOrder() as $rule) { - if (! in_array($rule[0], $fields)) { + if (! in_array($rule[0], $fields, true)) { $fields[] = $rule[0]; } } @@ -812,7 +812,7 @@ class LdapConnection implements Selectable, Inspectable $serverSorting = false;//$this->getCapabilities()->hasOid(LdapCapabilities::LDAP_SERVER_SORT_OID); if (! $serverSorting && $query->hasOrder()) { foreach ($query->getOrder() as $rule) { - if (! in_array($rule[0], $fields)) { + if (! in_array($rule[0], $fields, true)) { $fields[] = $rule[0]; } } @@ -858,7 +858,8 @@ class LdapConnection implements Selectable, Inspectable } elseif (ldap_count_entries($ds, $results) === 0) { if (in_array( ldap_errno($ds), - array(static::LDAP_SIZELIMIT_EXCEEDED, static::LDAP_ADMINLIMIT_EXCEEDED) + array(static::LDAP_SIZELIMIT_EXCEEDED, static::LDAP_ADMINLIMIT_EXCEEDED), + true )) { Logger::warning( 'Unable to request more than %u results. Does the server allow paged search requests? (%s)', From e408630e343313741e95b79625c0511c34a1b555 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 10 Nov 2015 13:41:08 +0100 Subject: [PATCH 63/84] LdapConnection: Do not require calling array_flip for method cleanupAttributes() Seems to be a relict of an earlier implementation.. refs #10370 --- .../Icinga/Protocol/Ldap/LdapCapabilities.php | 8 +--- .../Icinga/Protocol/Ldap/LdapConnection.php | 41 +++++++++++-------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/library/Icinga/Protocol/Ldap/LdapCapabilities.php b/library/Icinga/Protocol/Ldap/LdapCapabilities.php index 1ee64eaba..596618d79 100644 --- a/library/Icinga/Protocol/Ldap/LdapCapabilities.php +++ b/library/Icinga/Protocol/Ldap/LdapCapabilities.php @@ -308,12 +308,8 @@ class LdapCapabilities ldap_error($ds) ); } - $cap = new LdapCapabilities( - $connection->cleanupAttributes( - ldap_get_attributes($ds, $entry), - array_flip($fields) - ) - ); + + $cap = new LdapCapabilities($connection->cleanupAttributes(ldap_get_attributes($ds, $entry), $fields)); return $cap; } diff --git a/library/Icinga/Protocol/Ldap/LdapConnection.php b/library/Icinga/Protocol/Ldap/LdapConnection.php index 95d21b987..aa9a1c07e 100644 --- a/library/Icinga/Protocol/Ldap/LdapConnection.php +++ b/library/Icinga/Protocol/Ldap/LdapConnection.php @@ -731,12 +731,7 @@ class LdapConnection implements Selectable, Inspectable $unfoldAttribute = $query->getUnfoldAttribute(); do { if ($unfoldAttribute) { - $rows = $this->cleanupAttributes( - ldap_get_attributes($ds, $entry), - array_flip($fields), - $unfoldAttribute - ); - + $rows = $this->cleanupAttributes(ldap_get_attributes($ds, $entry), $fields, $unfoldAttribute); if (is_array($rows)) { // TODO: Register the DN the same way as a section name in the ArrayDatasource! foreach ($rows as $row) { @@ -760,7 +755,7 @@ class LdapConnection implements Selectable, Inspectable if (! $serverSorting || $offset === 0 || $offset < $count) { $entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes( ldap_get_attributes($ds, $entry), - array_flip($fields) + $fields ); } } @@ -874,12 +869,7 @@ class LdapConnection implements Selectable, Inspectable $entry = ldap_first_entry($ds, $results); do { if ($unfoldAttribute) { - $rows = $this->cleanupAttributes( - ldap_get_attributes($ds, $entry), - array_flip($fields), - $unfoldAttribute - ); - + $rows = $this->cleanupAttributes(ldap_get_attributes($ds, $entry), $fields, $unfoldAttribute); if (is_array($rows)) { // TODO: Register the DN the same way as a section name in the ArrayDatasource! foreach ($rows as $row) { @@ -903,7 +893,7 @@ class LdapConnection implements Selectable, Inspectable if (! $serverSorting || $offset === 0 || $offset < $count) { $entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes( ldap_get_attributes($ds, $entry), - array_flip($fields) + $fields ); } } @@ -965,8 +955,17 @@ class LdapConnection implements Selectable, Inspectable // necessary to create another array to map attributes case insensitively to their requested counterparts. // This does also apply the virtual alias handling. (Since an LDAP server does not handle such) $loweredFieldMap = array(); - foreach ($requestedFields as $name => $alias) { - $loweredFieldMap[strtolower($name)] = is_string($alias) ? $alias : $name; + foreach ($requestedFields as $alias => $name) { + $loweredName = strtolower($name); + if (isset($loweredFieldMap[$loweredName])) { + if (! is_array($loweredFieldMap[$loweredName])) { + $loweredFieldMap[$loweredName] = array($loweredFieldMap[$loweredName]); + } + + $loweredFieldMap[$loweredName][] = is_string($alias) ? $alias : $name; + } else { + $loweredFieldMap[$loweredName] = is_string($alias) ? $alias : $name; + } } $cleanedAttributes = array(); @@ -984,12 +983,18 @@ class LdapConnection implements Selectable, Inspectable $requestedAttributeName = isset($loweredFieldMap[strtolower($attribute_name)]) ? $loweredFieldMap[strtolower($attribute_name)] : $attribute_name; - $cleanedAttributes[$requestedAttributeName] = $attribute_value; + if (is_array($requestedAttributeName)) { + foreach ($requestedAttributeName as $requestedName) { + $cleanedAttributes[$requestedName] = $attribute_value; + } + } else { + $cleanedAttributes[$requestedAttributeName] = $attribute_value; + } } // The result may not contain all requested fields, so populate the cleaned // result with the missing fields and their value being set to null - foreach ($requestedFields as $name => $alias) { + foreach ($requestedFields as $alias => $name) { if (! is_string($alias)) { $alias = $name; } From cee639d6892e2ab0d860de8fccc67eaff235f815 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 10 Nov 2015 14:03:08 +0100 Subject: [PATCH 64/84] LdapConnection: Re-apply a query's filter on unfolded rows refs #10370 --- .../Icinga/Protocol/Ldap/LdapConnection.php | 70 ++++++++++++++----- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/library/Icinga/Protocol/Ldap/LdapConnection.php b/library/Icinga/Protocol/Ldap/LdapConnection.php index aa9a1c07e..9158dc5a4 100644 --- a/library/Icinga/Protocol/Ldap/LdapConnection.php +++ b/library/Icinga/Protocol/Ldap/LdapConnection.php @@ -702,11 +702,23 @@ class LdapConnection implements Selectable, Inspectable } } + $unfoldAttribute = $query->getUnfoldAttribute(); + if ($unfoldAttribute) { + foreach ($query->getFilter()->listFilteredColumns() as $filterColumn) { + $fieldKey = array_search($filterColumn, $fields, true); + if ($fieldKey === false || is_string($fieldKey)) { + $fields[] = $filterColumn; + } + } + } + $results = @ldap_search( $ds, $query->getBase() ?: $this->rootDn, (string) $query, - array_values($fields), + $unfoldAttribute + ? array_unique(array_values($fields)) + : array_values($fields), 0, // Attributes and values $serverSorting && $limit ? $offset + $limit : 0 ); @@ -728,20 +740,21 @@ class LdapConnection implements Selectable, Inspectable $count = 0; $entries = array(); $entry = ldap_first_entry($ds, $results); - $unfoldAttribute = $query->getUnfoldAttribute(); do { if ($unfoldAttribute) { $rows = $this->cleanupAttributes(ldap_get_attributes($ds, $entry), $fields, $unfoldAttribute); if (is_array($rows)) { // TODO: Register the DN the same way as a section name in the ArrayDatasource! foreach ($rows as $row) { - $count += 1; - if (! $serverSorting || $offset === 0 || $offset < $count) { - $entries[] = $row; - } + if ($query->getFilter()->matches($row)) { + $count += 1; + if (! $serverSorting || $offset === 0 || $offset < $count) { + $entries[] = $row; + } - if ($serverSorting && $limit > 0 && $limit === count($entries)) { - break; + if ($serverSorting && $limit > 0 && $limit === count($entries)) { + break; + } } } } else { @@ -813,10 +826,19 @@ class LdapConnection implements Selectable, Inspectable } } + $unfoldAttribute = $query->getUnfoldAttribute(); + if ($unfoldAttribute) { + foreach ($query->getFilter()->listFilteredColumns() as $filterColumn) { + $fieldKey = array_search($filterColumn, $fields, true); + if ($fieldKey === false || is_string($fieldKey)) { + $fields[] = $filterColumn; + } + } + } + $count = 0; $cookie = ''; $entries = array(); - $unfoldAttribute = $query->getUnfoldAttribute(); do { // Do not request the pagination control as a critical extension, as we want the // server to return results even if the paged search request cannot be satisfied @@ -835,7 +857,9 @@ class LdapConnection implements Selectable, Inspectable $ds, $base, $queryString, - array_values($fields), + $unfoldAttribute + ? array_unique(array_values($fields)) + : array_values($fields), 0, // Attributes and values $serverSorting && $limit ? $offset + $limit : 0 ); @@ -873,13 +897,15 @@ class LdapConnection implements Selectable, Inspectable if (is_array($rows)) { // TODO: Register the DN the same way as a section name in the ArrayDatasource! foreach ($rows as $row) { - $count += 1; - if (! $serverSorting || $offset === 0 || $offset < $count) { - $entries[] = $row; - } + if ($query->getFilter()->matches($row)) { + $count += 1; + if (! $serverSorting || $offset === 0 || $offset < $count) { + $entries[] = $row; + } - if ($serverSorting && $limit > 0 && $limit === count($entries)) { - break; + if ($serverSorting && $limit > 0 && $limit === count($entries)) { + break; + } } } } else { @@ -1010,6 +1036,14 @@ class LdapConnection implements Selectable, Inspectable && isset($cleanedAttributes[$unfoldAttribute]) && is_array($cleanedAttributes[$unfoldAttribute]) ) { + $siblings = array(); + foreach ($loweredFieldMap as $loweredName => $requestedNames) { + if (is_array($requestedNames) && in_array($unfoldAttribute, $requestedNames, true)) { + $siblings = array_diff($requestedNames, array($unfoldAttribute)); + break; + } + } + $values = $cleanedAttributes[$unfoldAttribute]; unset($cleanedAttributes[$unfoldAttribute]); $baseRow = (object) $cleanedAttributes; @@ -1017,6 +1051,10 @@ class LdapConnection implements Selectable, Inspectable foreach ($values as $value) { $row = clone $baseRow; $row->{$unfoldAttribute} = $value; + foreach ($siblings as $sibling) { + $row->{$sibling} = $value; + } + $rows[] = $row; } From ad5a43ce7b4bbc7640424c101129ac073aed94c5 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 10 Nov 2015 15:04:00 +0100 Subject: [PATCH 65/84] HostserviceproblemsummaryQuery: Group by "so.name1" fixes #10589 refs #10490 --- .../Backend/Ido/Query/HostserviceproblemsummaryQuery.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostserviceproblemsummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostserviceproblemsummaryQuery.php index ff9d27fb3..d5111abb8 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostserviceproblemsummaryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostserviceproblemsummaryQuery.php @@ -75,7 +75,7 @@ class HostserviceproblemsummaryQuery extends IdoQuery 's.service_object_id = so.object_id AND so.is_active = 1', array() ); - $this->select->group(array('s.host_object_id')); + $this->select->group('so.name1'); $this->joinedVirtualTables['services'] = true; } From 52606eb2e73a75c4d4baa8ed88262cc8a69a4e01 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 10 Nov 2015 15:58:44 +0100 Subject: [PATCH 66/84] Revert "monitoring: Fix handling of collated columns w/ PostgreSQL" This reverts commit f5ffa8047c97023b2b53b8404957c9ff1306ba85. refs #10364 refs #9954 refs #9955 --- .../Monitoring/Backend/Ido/Query/IdoQuery.php | 58 ++++++++++++++----- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php index 5c0cbba82..91b8e4ca0 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php @@ -485,22 +485,16 @@ abstract class IdoQuery extends DbQuery if ($filter->getExpression() === '*') { return; // Wildcard only filters are ignored so stop early here to avoid joining a table for nothing } + $alias = $filter->getColumn(); $this->requireColumn($alias); + if ($this->isCustomvar($alias)) { $column = $this->getCustomvarColumnName($alias); } else { $column = $this->aliasToColumnName($alias); } - if (isset($this->columnsWithoutCollation[$alias])) { - $expression = $filter->getExpression(); - if (is_array($expression)) { - $filter->setExpression(array_map('strtolower', $expression)); - } else { - $filter->setExpression(strtolower($expression)); - } - } $filter->setColumn($column); } else { foreach ($filter->filters() as $filter) { @@ -519,11 +513,48 @@ abstract class IdoQuery extends DbQuery return parent::addFilter($filter); } + /** + * Recurse the given filter and ensure that any string conversion is case-insensitive + * + * @param Filter $filter + */ + protected function lowerColumnsWithoutCollation(Filter $filter) + { + if ($filter instanceof FilterExpression) { + if ( + in_array($filter->getColumn(), $this->columnsWithoutCollation) + && strpos($filter->getColumn(), 'LOWER') !== 0 + ) { + $filter->setColumn('LOWER(' . $filter->getColumn() . ')'); + $expression = $filter->getExpression(); + if (is_array($expression)) { + $filter->setExpression(array_map('strtolower', $expression)); + } else { + $filter->setExpression(strtolower($expression)); + } + } + } else { + foreach ($filter->filters() as $chainedFilter) { + $this->lowerColumnsWithoutCollation($chainedFilter); + } + } + } + + protected function applyFilterSql($select) + { + if (! empty($this->columnsWithoutCollation)) { + $this->lowerColumnsWithoutCollation($this->filter); + } + + parent::applyFilterSql($select); + } + public function where($condition, $value = null) { if ($value === '*') { return $this; // Wildcard only filters are ignored so stop early here to avoid joining a table for nothing } + $this->requireColumn($condition); $col = $this->getMappedField($condition); if ($col === null) { @@ -571,6 +602,7 @@ abstract class IdoQuery extends DbQuery if (! empty($this->columnsWithoutCollation)) { return in_array($column, $this->columnsWithoutCollation) || strpos($column, 'LOWER') !== 0; } + return preg_match('/ COLLATE .+$/', $column) === 1; } @@ -601,11 +633,11 @@ abstract class IdoQuery extends DbQuery { $this->customVarsJoinTemplate = '%1$s = %2$s.object_id AND LOWER(%2$s.varname) = %3$s'; - foreach ($this->columnMap as $table => &$columns) { - foreach ($columns as $alias => &$column) { - if (false !== $pos = strpos($column, ' COLLATE')) { - $column = 'LOWER(' . substr($column, 0, $pos) . ')'; - $this->columnsWithoutCollation[$alias] = true; + foreach ($this->columnMap as $table => & $columns) { + foreach ($columns as $alias => & $column) { + $column = preg_replace('/ COLLATE .+$/', '', $column, -1, $count); + if ($count > 0) { + $this->columnsWithoutCollation[] = $this->getMappedField($alias); } $column = preg_replace( '/inet_aton\(([[:word:].]+)\)/i', From 3d735693dbc2e686e94858b51971311ffae75c83 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 10 Nov 2015 16:05:05 +0100 Subject: [PATCH 67/84] IdoQuery: Fix method isCaseInsensitive() returning true for .. everything .. ..with a PostgreSQL backend. refs #10364 --- .../library/Monitoring/Backend/Ido/Query/IdoQuery.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php index 91b8e4ca0..111d1e1bd 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php @@ -600,7 +600,7 @@ abstract class IdoQuery extends DbQuery } if (! empty($this->columnsWithoutCollation)) { - return in_array($column, $this->columnsWithoutCollation) || strpos($column, 'LOWER') !== 0; + return in_array($column, $this->columnsWithoutCollation) || strpos($column, 'LOWER') === 0; } return preg_match('/ COLLATE .+$/', $column) === 1; From a662fc9af0ba3a40c27fc38b6cd9d310d5bfd87e Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Tue, 10 Nov 2015 16:08:02 +0100 Subject: [PATCH 68/84] Controller: Re-add "view" as preserved column We're still utilizing this in the dashboard.. --- library/Icinga/Web/Controller.php | 1 + 1 file changed, 1 insertion(+) diff --git a/library/Icinga/Web/Controller.php b/library/Icinga/Web/Controller.php index 891b70329..ca6cb7e23 100644 --- a/library/Icinga/Web/Controller.php +++ b/library/Icinga/Web/Controller.php @@ -203,6 +203,7 @@ class Controller extends ModuleActionController 'sort', // setupSortControl() 'dir', // setupSortControl() 'backend', // Framework + 'view', // Framework '_dev' // Framework ); From 60a951a97ddb6f6b9e6aff01caf4ce3eee832f2e Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 11 Nov 2015 09:59:28 +0100 Subject: [PATCH 69/84] Logger: Add method getLevel() refs #10567 --- library/Icinga/Application/Logger.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/library/Icinga/Application/Logger.php b/library/Icinga/Application/Logger.php index 9203a2f75..08568431f 100644 --- a/library/Icinga/Application/Logger.php +++ b/library/Icinga/Application/Logger.php @@ -136,6 +136,16 @@ class Logger return $this; } + /** + * Return the logging level being used + * + * @return int + */ + public function getLevel() + { + return $this->level; + } + /** * Register the given message as config error * From c85bce72115209c739844545d703a33c05228c11 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 11 Nov 2015 10:01:00 +0100 Subject: [PATCH 70/84] LdapConnection: Add method ldapSearch() This will now emit a debug message for each issued search operation. refs #10567 --- .../Icinga/Protocol/Ldap/LdapConnection.php | 105 ++++++++++++++---- 1 file changed, 82 insertions(+), 23 deletions(-) diff --git a/library/Icinga/Protocol/Ldap/LdapConnection.php b/library/Icinga/Protocol/Ldap/LdapConnection.php index ceefc81b8..8ca034653 100644 --- a/library/Icinga/Protocol/Ldap/LdapConnection.php +++ b/library/Icinga/Protocol/Ldap/LdapConnection.php @@ -377,14 +377,7 @@ class LdapConnection implements Selectable, Inspectable } $ds = $this->getConnection(); - $results = @ldap_search( - $ds, - $query->getBase() ?: $this->getDn(), - (string) $query, - array('dn'), - 0, - 0 - ); + $results = $this->ldapSearch($query, array('dn')); if ($results === false) { if (ldap_errno($ds) !== self::LDAP_NO_SUCH_OBJECT) { @@ -701,12 +694,10 @@ class LdapConnection implements Selectable, Inspectable } } - $results = @ldap_search( - $ds, - $query->getBase() ?: $this->rootDn, - (string) $query, + $results = $this->ldapSearch( + $query, array_values($fields), - 0, // Attributes and values + 0, $serverSorting && $limit ? $offset + $limit : 0 ); if ($results === false) { @@ -799,8 +790,6 @@ class LdapConnection implements Selectable, Inspectable $limit = $query->getLimit(); $offset = $query->hasOffset() ? $query->getOffset() : 0; - $queryString = (string) $query; - $base = $query->getBase() ?: $this->rootDn; if ($fields === null) { $fields = $query->getColumns(); @@ -835,12 +824,10 @@ class LdapConnection implements Selectable, Inspectable )); } - $results = @ldap_search( - $ds, - $base, - $queryString, + $results = $this->ldapSearch( + $query, array_values($fields), - 0, // Attributes and values + 0, $serverSorting && $limit ? $offset + $limit : 0 ); if ($results === false) { @@ -850,8 +837,8 @@ class LdapConnection implements Selectable, Inspectable throw new LdapException( 'LDAP query "%s" (base %s) failed. Error: %s', - $queryString, - $base, + (string) $query, + $query->getBase() ?: $this->getDn(), ldap_error($ds) ); } elseif (ldap_count_entries($ds, $results) === 0) { @@ -932,7 +919,8 @@ class LdapConnection implements Selectable, Inspectable // pagedResultsControl with the size set to zero (0) and the cookie set to the last cookie returned by // the server: https://www.ietf.org/rfc/rfc2696.txt ldap_control_paged_result($ds, 0, false, $cookie); - ldap_search($ds, $base, $queryString); // Returns no entries, due to the page size + // Returns no entries, due to the page size + ldap_search($ds, $query->getBase() ?: $this->getDn(), (string) $query); } if (! $serverSorting && $query->hasOrder()) { @@ -1119,6 +1107,77 @@ class LdapConnection implements Selectable, Inspectable return $ds; } + /** + * Perform a LDAP search and return the result + * + * @param LdapQuery $query + * @param array $attributes An array of the required attributes + * @param int $attrsonly Should be set to 1 if only attribute types are wanted + * @param int $sizelimit Enables you to limit the count of entries fetched + * @param int $timelimit Sets the number of seconds how long is spend on the search + * @param int $deref + * + * @return resource|bool A search result identifier or false on error + */ + public function ldapSearch( + LdapQuery $query, + array $attributes = null, + $attrsonly = 0, + $sizelimit = 0, + $timelimit = 0, + $deref = LDAP_DEREF_NEVER + ) { + $queryString = (string) $query; + $baseDn = $query->getBase() ?: $this->getDn(); + + if (Logger::getInstance()->getLevel() === Logger::DEBUG) { + // We're checking the level by ourself to avoid rendering the ldapsearch commandline for nothing + $starttlsParam = $this->encryption === static::STARTTLS ? ' -ZZ' : ''; + $ldapUrl = ($this->encryption === static::LDAPS ? 'ldaps://' : 'ldap://') + . $this->hostname + . ($this->port ? ':' . $this->port : ''); + + if ($this->bound) { + $bindParams = ' -D "' . $this->bindDn . '"' . ($this->bindPw ? ' -w "' . $this->bindPw . '"' : ''); + } + + if ($deref === LDAP_DEREF_NEVER) { + $derefName = 'never'; + } elseif ($deref === LDAP_DEREF_ALWAYS) { + $derefName = 'always'; + } elseif ($deref === LDAP_DEREF_SEARCHING) { + $derefName = 'search'; + } else { // $deref === LDAP_DEREF_FINDING + $derefName = 'find'; + } + + Logger::debug("Issueing LDAP search. Use '%s' to reproduce.", sprintf( + 'ldapsearch -P 3%s -H "%s"%s -b "%s" -s "sub" -z %u -l %u -a "%s"%s%s%s', + $starttlsParam, + $ldapUrl, + $bindParams, + $baseDn, + $sizelimit, + $timelimit, + $derefName, + $attrsonly ? ' -A' : '', + $queryString ? ' "' . $queryString . '"' : '', + $attributes ? ' "' . join('" "', $attributes) . '"' : '' + )); + } + + return @ldap_search( + $this->getConnection(), + $baseDn, + $queryString, + $attributes, + $attrsonly, + $sizelimit, + $timelimit, + $deref + ); + } + /** * Create an LDAP entry * From 453aa864cc236b43bbacf10c050aa4caae844cbf Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 11 Nov 2015 11:38:32 +0100 Subject: [PATCH 71/84] LdapUserGroupBackend: Set the appropriate base dn when resolving dns refs #10567 --- .../Icinga/Authentication/UserGroup/LdapUserGroupBackend.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index 8c3fc3c3d..bac2f4525 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -507,6 +507,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt ->select() ->from($this->userClass, array()) ->where($this->userNameAttribute, $name) + ->setBase($this->userBaseDn) ->setUsePagedResults(false) ->fetchDn(); if ($userDn) { @@ -517,6 +518,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt ->select() ->from($this->groupClass, array()) ->where($this->groupNameAttribute, $name) + ->setBase($this->groupBaseDn) ->setUsePagedResults(false) ->fetchDn(); if ($groupDn) { From 31b584b3388a596fed007dbeda9849fa550d472e Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 11 Nov 2015 12:44:08 +0100 Subject: [PATCH 72/84] LdapConnection: Fix method fetchOne() The method suffered from multiple issues: * Actual NULL values were interpreted as if the row does not have any cols * Which attribute's value got returned was dependent on the result set instead of the desired columns refs #10567 --- .../Icinga/Protocol/Ldap/LdapConnection.php | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/library/Icinga/Protocol/Ldap/LdapConnection.php b/library/Icinga/Protocol/Ldap/LdapConnection.php index 77872781c..1cad06680 100644 --- a/library/Icinga/Protocol/Ldap/LdapConnection.php +++ b/library/Icinga/Protocol/Ldap/LdapConnection.php @@ -476,8 +476,28 @@ class LdapConnection implements Selectable, Inspectable */ public function fetchOne(LdapQuery $query, array $fields = null) { - $row = (array) $this->fetchRow($query, $fields); - return array_shift($row) ?: false; + $row = $this->fetchRow($query, $fields); + if ($row === false) { + return false; + } + + $values = get_object_vars($row); + if (empty($values)) { + return false; + } + + if ($fields === null) { + // Fetch the desired columns from the query if not explicitly overriden in the method's parameter + $fields = $query->getColumns(); + } + + if (empty($fields)) { + // The desired columns may be empty independently whether provided by the query or the method's parameter + return array_shift($values); + } + + $alias = key($fields); + return $values[is_string($alias) ? $alias : $fields[$alias]]; } /** From cf193f2c1b06c21c56b52e9573847c33b68779e2 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 11 Nov 2015 12:48:01 +0100 Subject: [PATCH 73/84] RepositoryQuery: Initialize property $query before requiring a new table Since $this gets passed to Repository::requireTable() it may be possible that some repository tries to access the underlying native query so we need to ensure that we're able to actually provide it. refs #10567 --- library/Icinga/Repository/RepositoryQuery.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/library/Icinga/Repository/RepositoryQuery.php b/library/Icinga/Repository/RepositoryQuery.php index 532cae7c6..d227d9fde 100644 --- a/library/Icinga/Repository/RepositoryQuery.php +++ b/library/Icinga/Repository/RepositoryQuery.php @@ -86,9 +86,8 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera */ public function from($target, array $columns = null) { - $this->query = $this->repository->getDataSource()->select()->from( - $this->repository->requireTable($target, $this) - ); + $this->query = $this->repository->getDataSource()->select(); + $this->query->from($this->repository->requireTable($target, $this)); $this->query->columns($this->prepareQueryColumns($target, $columns)); $this->target = $target; return $this; From 8bf4e8d2171a1bae07a902847cd8dbff0d8ba75a Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 11 Nov 2015 12:54:49 +0100 Subject: [PATCH 74/84] LdapUserGroupBackend: Set a query's base DN when a table gets required This ensures that the query receives the correct base DN even if the table gets adjusted by calling from() subsequently. refs #10567 --- .../UserGroup/LdapUserGroupBackend.php | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index 2c366338a..122453990 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -397,20 +397,6 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt return $this->ambiguousMemberAttribute; } - /** - * 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 virtual tables * @@ -575,8 +561,11 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt */ public function requireTable($table, RepositoryQuery $query = null) { - if ($query !== null && $table === 'group' && $this->groupFilter) { - $query->getQuery()->setNativeFilter($this->groupFilter); + if ($query !== null) { + $query->getQuery()->setBase($this->groupBaseDn); + if ($table === 'group' && $this->groupFilter) { + $query->getQuery()->setNativeFilter($this->groupFilter); + } } return parent::requireTable($table, $query); From d2cc854a617a97be7b6168510092badd6febd8fe Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 11 Nov 2015 12:55:17 +0100 Subject: [PATCH 75/84] LdapUserBackend: Set a query's base DN when a table gets required This ensures that the query receives the correct base DN even if the table gets adjusted by calling from() subsequently. refs #10567 --- .../Authentication/User/LdapUserBackend.php | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/library/Icinga/Authentication/User/LdapUserBackend.php b/library/Icinga/Authentication/User/LdapUserBackend.php index 930ac4c99..11fc0093e 100644 --- a/library/Icinga/Authentication/User/LdapUserBackend.php +++ b/library/Icinga/Authentication/User/LdapUserBackend.php @@ -190,24 +190,6 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In ->setFilter($config->filter); } - /** - * 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->baseDn); - if ($this->filter) { - $query->getQuery()->setNativeFilter($this->filter); - } - - return $query; - } - /** * Initialize this repository's virtual tables * @@ -335,6 +317,28 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In return ((int) $value) >= $bigBang->diff($now)->days; } + /** + * Validate that the requested table exists + * + * @param string $table The table to validate + * @param RepositoryQuery $query An optional query to pass as context + * + * @return string + * + * @throws ProgrammingError In case the given table does not exist + */ + public function requireTable($table, RepositoryQuery $query = null) + { + if ($query !== null) { + $query->getQuery()->setBase($this->baseDn); + if ($this->filter) { + $query->getQuery()->setNativeFilter($this->filter); + } + } + + return parent::requireTable($table, $query); + } + /** * Validate that the given column is a valid query target and return it or the actual name if it's an alias * From cf7c99ff02f5f9a55c82fdd5fb6fe9c004d94874 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 11 Nov 2015 13:03:55 +0100 Subject: [PATCH 76/84] puppet: Correct DN used to add jdoe as member to the group Users --- .puppet/profiles/icingaweb2_dev/files/openldap/users.ldif | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.puppet/profiles/icingaweb2_dev/files/openldap/users.ldif b/.puppet/profiles/icingaweb2_dev/files/openldap/users.ldif index f5ad802a3..1526344c9 100644 --- a/.puppet/profiles/icingaweb2_dev/files/openldap/users.ldif +++ b/.puppet/profiles/icingaweb2_dev/files/openldap/users.ldif @@ -37,7 +37,7 @@ userpassword: password dn: cn=Users,ou=groups,dc=icinga,dc=org objectClass: groupOfUniqueNames cn: Users -uniqueMember: cn=Jon Doe,ou=people,dc=icinga,dc=org +uniqueMember: cn=John Doe,ou=people,dc=icinga,dc=org uniqueMember: cn=Jane Smith,ou=people,dc=icinga,dc=org uniqueMember: cn=John Q. Public,ou=people,dc=icinga,dc=org uniqueMember: cn=Richard Roe,ou=people,dc=icinga,dc=org From d896972f5fcb66c7d0042f75fced4dbd452d3aff Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 11 Nov 2015 13:04:26 +0100 Subject: [PATCH 77/84] puppet: Add new openldap group based on the nis.schema --- .../profiles/icingaweb2_dev/files/openldap/users.ldif | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.puppet/profiles/icingaweb2_dev/files/openldap/users.ldif b/.puppet/profiles/icingaweb2_dev/files/openldap/users.ldif index 1526344c9..d0760dafc 100644 --- a/.puppet/profiles/icingaweb2_dev/files/openldap/users.ldif +++ b/.puppet/profiles/icingaweb2_dev/files/openldap/users.ldif @@ -41,3 +41,12 @@ uniqueMember: cn=John Doe,ou=people,dc=icinga,dc=org uniqueMember: cn=Jane Smith,ou=people,dc=icinga,dc=org uniqueMember: cn=John Q. Public,ou=people,dc=icinga,dc=org uniqueMember: cn=Richard Roe,ou=people,dc=icinga,dc=org + +dn: cn=PosixUsers,ou=groups,dc=icinga,dc=org +objectClass: posixGroup +cn: PosixUsers +gidNumber: 2001 +memberUid: jdoe +memberUid: jsmith +memberUid: jqpublic +memberUid: rroe From 0b2b1c5d1e8e426c974d82e95b17e44149ac7c44 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 11 Nov 2015 14:33:00 +0100 Subject: [PATCH 78/84] IdoQuery: Fix that PostgreSQL queries use LOWER() on non-CI columns refs #10364 refs #9954 --- .../Monitoring/Backend/Ido/Query/IdoQuery.php | 82 ++++++++----------- 1 file changed, 33 insertions(+), 49 deletions(-) diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php index 111d1e1bd..d37ce2374 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php @@ -55,14 +55,14 @@ abstract class IdoQuery extends DbQuery protected $prefix; /** - * An array to map aliases to table names + * An array to map aliases to column names * * @var array */ protected $idxAliasColumn; /** - * An array to map aliases to column names + * An array to map aliases to table names * * @var array */ @@ -400,14 +400,14 @@ abstract class IdoQuery extends DbQuery protected static $idoVersion; /** - * List of columns where the COLLATE SQL-instruction has been removed + * List of column aliases mapped to their table where the COLLATE SQL-instruction has been removed * * This list is being populated in case of a PostgreSQL backend only, * to ensure case-insensitive string comparison in WHERE clauses. * * @var array */ - protected $columnsWithoutCollation = array(); + protected $caseInsensitiveColumns; /** * Return true when the column is an aggregate column @@ -493,6 +493,15 @@ abstract class IdoQuery extends DbQuery $column = $this->getCustomvarColumnName($alias); } else { $column = $this->aliasToColumnName($alias); + if (isset($this->caseInsensitiveColumns[$this->aliasToTableName($alias)][$alias])) { + $column = 'LOWER(' . $column . ')'; + $expression = $filter->getExpression(); + if (is_array($expression)) { + $filter->setExpression(array_map('strtolower', $expression)); + } else { + $filter->setExpression(strtolower($expression)); + } + } } $filter->setColumn($column); @@ -513,42 +522,6 @@ abstract class IdoQuery extends DbQuery return parent::addFilter($filter); } - /** - * Recurse the given filter and ensure that any string conversion is case-insensitive - * - * @param Filter $filter - */ - protected function lowerColumnsWithoutCollation(Filter $filter) - { - if ($filter instanceof FilterExpression) { - if ( - in_array($filter->getColumn(), $this->columnsWithoutCollation) - && strpos($filter->getColumn(), 'LOWER') !== 0 - ) { - $filter->setColumn('LOWER(' . $filter->getColumn() . ')'); - $expression = $filter->getExpression(); - if (is_array($expression)) { - $filter->setExpression(array_map('strtolower', $expression)); - } else { - $filter->setExpression(strtolower($expression)); - } - } - } else { - foreach ($filter->filters() as $chainedFilter) { - $this->lowerColumnsWithoutCollation($chainedFilter); - } - } - } - - protected function applyFilterSql($select) - { - if (! empty($this->columnsWithoutCollation)) { - $this->lowerColumnsWithoutCollation($this->filter); - } - - parent::applyFilterSql($select); - } - public function where($condition, $value = null) { if ($value === '*') { @@ -582,28 +555,37 @@ abstract class IdoQuery extends DbQuery } /** - * Return whether the given alias or column name provides case insensitive value comparison + * Return whether the given alias provides case insensitive value comparison * - * @param string $aliasOrColumn + * @param string $alias * * @return bool */ - public function isCaseInsensitive($aliasOrColumn) + public function isCaseInsensitive($alias) { - if ($this->isCustomVar($aliasOrColumn)) { + if ($this->isCustomVar($alias)) { return false; } - $column = $this->getMappedField($aliasOrColumn) ?: $aliasOrColumn; + $column = $this->getMappedField($alias); if (! $column) { return false; } - if (! empty($this->columnsWithoutCollation)) { - return in_array($column, $this->columnsWithoutCollation) || strpos($column, 'LOWER') === 0; + if (empty($this->caseInsensitiveColumns)) { + return preg_match('/ COLLATE .+$/', $column) === 1; } - return preg_match('/ COLLATE .+$/', $column) === 1; + if (strpos($column, 'LOWER') === 0) { + return true; + } + + $table = $this->aliasToTableName($alias); + if (! $table) { + return false; + } + + return isset($this->caseInsensitiveColumns[$table][$alias]); } /** @@ -635,10 +617,12 @@ abstract class IdoQuery extends DbQuery '%1$s = %2$s.object_id AND LOWER(%2$s.varname) = %3$s'; foreach ($this->columnMap as $table => & $columns) { foreach ($columns as $alias => & $column) { + // Using a regex here because COLLATE may occur anywhere in the string $column = preg_replace('/ COLLATE .+$/', '', $column, -1, $count); if ($count > 0) { - $this->columnsWithoutCollation[] = $this->getMappedField($alias); + $this->caseInsensitiveColumns[$table][$alias] = true; } + $column = preg_replace( '/inet_aton\(([[:word:].]+)\)/i', '(CASE WHEN $1 ~ \'(?:[0-9]{1,3}\\\\.){3}[0-9]{1,3}\' THEN $1::inet - \'0.0.0.0\' ELSE NULL END)', From 22f966db43490471df2520571c186ea824274283 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 11 Nov 2015 15:06:18 +0100 Subject: [PATCH 79/84] DbRepository: Fix that PostgreSQL queries use LOWER() on non-CI columns refs #10364 refs #9954 --- library/Icinga/Repository/DbRepository.php | 35 +++++++++++++--------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/library/Icinga/Repository/DbRepository.php b/library/Icinga/Repository/DbRepository.php index 9292a208b..5c946d173 100644 --- a/library/Icinga/Repository/DbRepository.php +++ b/library/Icinga/Repository/DbRepository.php @@ -93,20 +93,20 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, protected $statementColumnAliasMap; /** - * List of columns where the COLLATE SQL-instruction has been removed + * List of column names or aliases mapped to their table where the COLLATE SQL-instruction has been removed * * This list is being populated in case of a PostgreSQL backend only, * to ensure case-insensitive string comparison in WHERE clauses. * * @var array */ - protected $columnsWithoutCollation; + protected $caseInsensitiveColumns; /** * Create a new DB repository object * * In case $this->queryColumns has already been initialized, this initializes - * $this->columnsWithoutCollation in case of a PostgreSQL connection. + * $this->caseInsensitiveColumns in case of a PostgreSQL connection. * * @param DbConnection $ds The datasource to use */ @@ -114,7 +114,6 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, { parent::__construct($ds); - $this->columnsWithoutCollation = array(); if ($ds->getDbType() === 'pgsql' && $this->queryColumns !== null) { $this->queryColumns = $this->removeCollateInstruction($this->queryColumns); } @@ -123,7 +122,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, /** * Return the query columns being provided * - * Initializes $this->columnsWithoutCollation in case of a PostgreSQL connection. + * Initializes $this->caseInsensitiveColumns in case of a PostgreSQL connection. * * @return array */ @@ -174,11 +173,12 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, */ protected function removeCollateInstruction($queryColumns) { - foreach ($queryColumns as & $columns) { - foreach ($columns as & $column) { + foreach ($queryColumns as $table => & $columns) { + foreach ($columns as $alias => & $column) { + // Using a regex here because COLLATE may occur anywhere in the string $column = preg_replace('/ COLLATE .+$/', '', $column, -1, $count); if ($count > 0) { - $this->columnsWithoutCollation[] = $column; + $this->caseInsensitiveColumns[$table][is_string($alias) ? $alias : $column] = true; } } } @@ -607,22 +607,29 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, */ public function requireFilter($table, Filter $filter, RepositoryQuery $query = null, $clone = true) { - $filter = parent::requireFilter($table, $filter, $query, $clone); + if (! empty($this->caseInsensitiveColumns) && $filter->isExpression()) { + $alias = $filter->getColumn(); + if (! $this->validateQueryColumnAssociation($table, $alias)) { + $origin = $this->findTableName($alias); // It might be a joined column + } else { + $origin = $table; + } - if ($filter->isExpression()) { - $column = $filter->getColumn(); - if (in_array($column, $this->columnsWithoutCollation) && strpos($column, 'LOWER') !== 0) { - $filter->setColumn('LOWER(' . $column . ')'); + if ($origin && isset($this->caseInsensitiveColumns[$origin][$alias])) { + $filter = parent::requireFilter($table, $filter, $query, $clone); + $filter->setColumn('LOWER(' . $filter->getColumn() . ')'); $expression = $filter->getExpression(); if (is_array($expression)) { $filter->setExpression(array_map('strtolower', $expression)); } else { $filter->setExpression(strtolower($expression)); } + + return $filter; } } - return $filter; + return parent::requireFilter($table, $filter, $query, $clone); } /** From 39f4d869b75b73ac3f9366d5f7b93bb31e1c9234 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 11 Nov 2015 15:25:40 +0100 Subject: [PATCH 80/84] IdoQuery: Fix that PostgreSQL queries do not apply LOWER() on order cols refs #10364 refs #9955 --- .../Monitoring/Backend/Ido/Query/IdoQuery.php | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php index d37ce2374..6d373daaa 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php @@ -421,26 +421,32 @@ abstract class IdoQuery extends DbQuery } /** - * Order the result by the given column + * Order the result by the given alias * - * @param string $columnOrAlias The column or column alias to order by - * @param int $dir The sort direction or null to use default direction + * @param string $alias The column alias to order by + * @param int $dir The sort direction or null to use the default direction * - * @return $this Fluent interface + * @return $this */ - public function order($columnOrAlias, $dir = null) + public function order($alias, $dir = null) { - $this->requireColumn($columnOrAlias); - $this->orderColumns[$columnOrAlias] = $columnOrAlias; - if ($this->isCustomvar($columnOrAlias)) { - $columnOrAlias = $this->getCustomvarColumnName($columnOrAlias); - } elseif ($this->hasAliasName($columnOrAlias)) { - $columnOrAlias = $this->aliasToColumnName($columnOrAlias); + $this->requireColumn($alias); + + if ($this->isCustomvar($alias)) { + $column = $this->getCustomvarColumnName($alias); + } elseif ($this->hasAliasName($alias)) { + $column = $this->aliasToColumnName($alias); + $table = $this->aliasToTableName($alias); + if (isset($this->caseInsensitiveColumns[$table][$alias])) { + $column = 'LOWER(' . $column . ')'; + } } else { - Logger::info('Can\'t order by column ' . $columnOrAlias); + Logger::info('Can\'t order by column ' . $alias); return $this; } - return parent::order($columnOrAlias, $dir); + + $this->orderColumns[$alias] = $alias; + return parent::order($column, $dir); } /** From d2b8ed243fa567f881a6499d55120f06aed8eaa2 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 11 Nov 2015 16:05:54 +0100 Subject: [PATCH 81/84] Repository: Accept parameter $filter in method requireFiltercolumn() This allows to adjust more than the name of the column if necessary. refs #10364 --- library/Icinga/Repository/DbRepository.php | 5 +++-- library/Icinga/Repository/Repository.php | 6 ++++-- library/Icinga/Repository/RepositoryQuery.php | 5 +---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/library/Icinga/Repository/DbRepository.php b/library/Icinga/Repository/DbRepository.php index 5c946d173..379a754cf 100644 --- a/library/Icinga/Repository/DbRepository.php +++ b/library/Icinga/Repository/DbRepository.php @@ -688,19 +688,20 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, * @param string $name The name or alias of the column to validate * @param RepositoryQuery $query An optional query to pass as context, * if not given the column is considered being used for a statement filter + * @param FilterExpression $filter An optional filter to pass as context * * @return string The given column's name * * @throws QueryException In case the given column is not a valid filter column */ - public function requireFilterColumn($table, $name, RepositoryQuery $query = null) + public function requireFilterColumn($table, $name, RepositoryQuery $query = null, FilterExpression $filter = null) { if ($query === null) { return $this->requireStatementColumn($table, $name); } if ($this->validateQueryColumnAssociation($table, $name)) { - return parent::requireFilterColumn($table, $name, $query); + return parent::requireFilterColumn($table, $name, $query, $filter); } return $this->joinColumn($name, $table, $query); diff --git a/library/Icinga/Repository/Repository.php b/library/Icinga/Repository/Repository.php index 680b36def..824f5c78b 100644 --- a/library/Icinga/Repository/Repository.php +++ b/library/Icinga/Repository/Repository.php @@ -6,6 +6,7 @@ namespace Icinga\Repository; use DateTime; use Icinga\Application\Logger; use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterExpression; use Icinga\Data\Selectable; use Icinga\Exception\ProgrammingError; use Icinga\Exception\QueryException; @@ -872,7 +873,7 @@ abstract class Repository implements Selectable if ($filter->isExpression()) { $column = $filter->getColumn(); - $filter->setColumn($this->requireFilterColumn($table, $column, $query)); + $filter->setColumn($this->requireFilterColumn($table, $column, $query, $filter)); $filter->setExpression($this->persistColumn($table, $column, $filter->getExpression(), $query)); } elseif ($filter->isChain()) { foreach ($filter->filters() as $chainOrExpression) { @@ -1049,12 +1050,13 @@ abstract class Repository implements Selectable * @param string $table The table where to look for the column or alias * @param string $name The name or alias of the column to validate * @param RepositoryQuery $query An optional query to pass as context (unused by the base implementation) + * @param FilterExpression $filter An optional filter to pass as context (unused by the base implementation) * * @return string The given column's name * * @throws QueryException In case the given column is not a valid filter column */ - public function requireFilterColumn($table, $name, RepositoryQuery $query = null) + public function requireFilterColumn($table, $name, RepositoryQuery $query = null, FilterExpression $filter = null) { if (($column = $this->resolveQueryColumnAlias($table, $name)) !== null) { $alias = $name; diff --git a/library/Icinga/Repository/RepositoryQuery.php b/library/Icinga/Repository/RepositoryQuery.php index d227d9fde..af6643199 100644 --- a/library/Icinga/Repository/RepositoryQuery.php +++ b/library/Icinga/Repository/RepositoryQuery.php @@ -206,10 +206,7 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera */ public function where($column, $value = null) { - $this->query->where( - $this->repository->requireFilterColumn($this->target, $column, $this), - $this->repository->persistColumn($this->target, $column, $value, $this) - ); + $this->addFilter(Filter::where($column, $value)); return $this; } From cbd16291768f2018a4017307ee3d78d974d7cd0b Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Wed, 11 Nov 2015 17:13:41 +0100 Subject: [PATCH 82/84] DataView/Host|ServiceStatus: allow hard_state cols --- modules/monitoring/library/Monitoring/DataView/Hoststatus.php | 1 + .../monitoring/library/Monitoring/DataView/Servicestatus.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/modules/monitoring/library/Monitoring/DataView/Hoststatus.php b/modules/monitoring/library/Monitoring/DataView/Hoststatus.php index 582ac21ca..0ab9dd92e 100644 --- a/modules/monitoring/library/Monitoring/DataView/Hoststatus.php +++ b/modules/monitoring/library/Monitoring/DataView/Hoststatus.php @@ -18,6 +18,7 @@ class HostStatus extends DataView 'host_address', 'host_address6', 'host_state', + 'host_hard_state', 'host_state_type', 'host_handled', 'host_unhandled', diff --git a/modules/monitoring/library/Monitoring/DataView/Servicestatus.php b/modules/monitoring/library/Monitoring/DataView/Servicestatus.php index 9cd3be344..db44f62c6 100644 --- a/modules/monitoring/library/Monitoring/DataView/Servicestatus.php +++ b/modules/monitoring/library/Monitoring/DataView/Servicestatus.php @@ -15,6 +15,7 @@ class ServiceStatus extends DataView 'host_name', 'host_display_name', 'host_state', + 'host_hard_state', 'host_state_type', 'host_last_state_change', 'host_address', @@ -24,6 +25,7 @@ class ServiceStatus extends DataView 'service_description', 'service_display_name', 'service_state', + 'service_hard_state', 'service_in_downtime', 'service_acknowledged', 'service_handled', From 439fc28d0fc4e9be93c1b542e42f021fff4a29e0 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 12 Nov 2015 08:23:35 +0100 Subject: [PATCH 83/84] DbRepository: Add missing use statement for class FilterExpression refs #10364 --- library/Icinga/Repository/DbRepository.php | 1 + 1 file changed, 1 insertion(+) diff --git a/library/Icinga/Repository/DbRepository.php b/library/Icinga/Repository/DbRepository.php index 379a754cf..12ef7ebe6 100644 --- a/library/Icinga/Repository/DbRepository.php +++ b/library/Icinga/Repository/DbRepository.php @@ -6,6 +6,7 @@ namespace Icinga\Repository; use Icinga\Data\Db\DbConnection; use Icinga\Data\Extensible; use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterExpression; use Icinga\Data\Reducible; use Icinga\Data\Updatable; use Icinga\Exception\IcingaException; From dc7756c5996a61e2721d7bdda289fdaf154e0af7 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Thu, 12 Nov 2015 09:13:46 +0100 Subject: [PATCH 84/84] DbRepository: Fix that PostgreSQL queries do not apply LOWER on order cols refs #10364 refs #9955 --- library/Icinga/Repository/DbRepository.php | 80 +++++++++------------- 1 file changed, 34 insertions(+), 46 deletions(-) diff --git a/library/Icinga/Repository/DbRepository.php b/library/Icinga/Repository/DbRepository.php index 12ef7ebe6..a18a47e73 100644 --- a/library/Icinga/Repository/DbRepository.php +++ b/library/Icinga/Repository/DbRepository.php @@ -592,47 +592,6 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, } } - /** - * Recurse the given filter, require each column for the given table and convert all values - * - * In case of a PostgreSQL connection, this applies LOWER() on the column and strtolower() - * on the value if a COLLATE SQL-instruction is part of the resolved column. - * - * @param string $table The table being filtered - * @param Filter $filter The filter to recurse - * @param RepositoryQuery $query An optional query to pass as context - * (Directly passed through to $this->requireFilterColumn) - * @param bool $clone Whether to clone $filter first - * - * @return Filter The udpated filter - */ - public function requireFilter($table, Filter $filter, RepositoryQuery $query = null, $clone = true) - { - if (! empty($this->caseInsensitiveColumns) && $filter->isExpression()) { - $alias = $filter->getColumn(); - if (! $this->validateQueryColumnAssociation($table, $alias)) { - $origin = $this->findTableName($alias); // It might be a joined column - } else { - $origin = $table; - } - - if ($origin && isset($this->caseInsensitiveColumns[$origin][$alias])) { - $filter = parent::requireFilter($table, $filter, $query, $clone); - $filter->setColumn('LOWER(' . $filter->getColumn() . ')'); - $expression = $filter->getExpression(); - if (is_array($expression)) { - $filter->setExpression(array_map('strtolower', $expression)); - } else { - $filter->setExpression(strtolower($expression)); - } - - return $filter; - } - } - - return parent::requireFilter($table, $filter, $query, $clone); - } - /** * Return the alias for the given query column name or null in case the query column name does not exist * @@ -683,7 +642,8 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, * Validate that the given column is a valid filter target and return it or the actual name if it's an alias * * Attempts to join the given column from a different table if its association to the given table cannot be - * verified. + * verified. In case of a PostgreSQL connection and if a COLLATE SQL-instruction is part of the resolved column, + * this applies LOWER() on the column and, if given, strtolower() on the filter's expression. * * @param string $table The table where to look for the column or alias * @param string $name The name or alias of the column to validate @@ -697,15 +657,43 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, */ public function requireFilterColumn($table, $name, RepositoryQuery $query = null, FilterExpression $filter = null) { + $joined = false; if ($query === null) { - return $this->requireStatementColumn($table, $name); + $column = $this->requireStatementColumn($table, $name); + } elseif ($this->validateQueryColumnAssociation($table, $name)) { + $column = parent::requireFilterColumn($table, $name, $query, $filter); + } else { + $column = $this->joinColumn($name, $table, $query); + $joined = true; } - if ($this->validateQueryColumnAssociation($table, $name)) { - return parent::requireFilterColumn($table, $name, $query, $filter); + if (! empty($this->caseInsensitiveColumns)) { + if ($joined) { + $table = $this->findTableName($name); + } + + if ($column === $name) { + if ($query === null) { + $name = $this->reassembleStatementColumnAlias($table, $name); + } else { + $name = $this->reassembleQueryColumnAlias($table, $name); + } + } + + if (isset($this->caseInsensitiveColumns[$table][$name])) { + $column = 'LOWER(' . $column . ')'; + if ($filter !== null) { + $expression = $filter->getExpression(); + if (is_array($expression)) { + $filter->setExpression(array_map('strtolower', $expression)); + } else { + $filter->setExpression(strtolower($expression)); + } + } + } } - return $this->joinColumn($name, $table, $query); + return $column; } /**