From 9c96a80cebfe7429bdf4574731443f0bcc0f8a27 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Thu, 20 Oct 2022 11:40:48 +0200 Subject: [PATCH 1/2] Introduce class `UserSuggestions` --- library/Icinga/Data/UserSuggestions.php | 159 ++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 library/Icinga/Data/UserSuggestions.php diff --git a/library/Icinga/Data/UserSuggestions.php b/library/Icinga/Data/UserSuggestions.php new file mode 100644 index 0000000000..6e0d45ee5c --- /dev/null +++ b/library/Icinga/Data/UserSuggestions.php @@ -0,0 +1,159 @@ +userGroupBackend = $userGroupBackend; + + return $this; + } + + /** + * Get User group backend + * + * @return UserGroupBackendInterface + */ + public function getUserGroupBackend(): UserGroupBackendInterface + { + return $this->userGroupBackend; + } + + /** + * Set User group name + * + * @param string $userGroupName + * + * @return $this + */ + public function setUserGroupName(string $userGroupName): self + { + $this->userGroupName = $userGroupName; + + return $this; + } + + /** + * Get User group name + * + * @return string + */ + public function getUserGroupName(): string + { + return $this->userGroupName; + } + + /** + * Get user backends + * + * @return array + */ + public function getUserBackends(): array + { + return $this->userBackends; + } + + /** + * Set user backends + * + * @param array $userBackends + * + * @return $this + */ + public function setUserBackends(array $userBackends): self + { + $this->userBackends = $userBackends; + + return $this; + } + + protected function fetchSuggestions(string $searchTerm, array $exclude = []) + { + $filter = $this->prepareFilter($searchTerm, $exclude); + $suggestions = []; + foreach ($this->getUserBackends() as $userBackend) { + try { + if ($userBackend instanceof DomainAwareInterface) { + $domain = $userBackend->getDomain(); + } else { + $domain = null; + } + + $users = $userBackend->select(['user_name']) + ->limit(self::DEFAULT_LIMIT) + ->applyFilter($filter) + ->fetchColumn(); + + foreach ($users as $userName) { + $userObj = (new User($userName))->setDomain($domain); + $suggestions[] = $userObj->getUsername(); + } + } catch (Exception $e) { + Logger::error($e); + Notification::warning(sprintf( + t('Failed to fetch any users from backend %s. Please check your log'), + $userBackend->getName() + )); + } + } + + return array_unique($suggestions); + } + + /** + * Prepare the filter for db query + * + * @param string $searchTerm + * @param array $exclude + * + * @return Filter + */ + private function prepareFilter(string $searchTerm, array $exclude = []): Filter + { + $filter = Filter::where('user_name', $searchTerm); + + $members = $this->getUserGroupBackend() + ->select() + ->from('group_membership', ['user_name']) + ->where('group_name', $this->getUserGroupName()) + ->fetchColumn(); + + $members = array_merge($members, $exclude); + + if (! empty($members)) { + $filter->andFilter( + Filter::not(Filter::where('user_name', $members)) + ); + } + + return $filter; + } +} From 2adad04bf5f71fac52dc5ed5f2645c3a49a60f3f Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Thu, 20 Oct 2022 11:41:30 +0200 Subject: [PATCH 2/2] Implement new search field to add user as group member * Make AddMemberForm an Ipl form extending `SimpleSearchField` --- application/controllers/GroupController.php | 51 ++++++-- .../forms/Config/UserGroup/AddMemberForm.php | 119 +++--------------- 2 files changed, 57 insertions(+), 113 deletions(-) diff --git a/application/controllers/GroupController.php b/application/controllers/GroupController.php index d18397cf49..e07b2c181d 100644 --- a/application/controllers/GroupController.php +++ b/application/controllers/GroupController.php @@ -9,6 +9,7 @@ use Icinga\Data\DataArray\ArrayDatasource; use Icinga\Data\Filter\Filter; use Icinga\Data\Reducible; +use Icinga\Data\UserSuggestions; use Icinga\Exception\NotFoundError; use Icinga\Forms\Config\UserGroup\AddMemberForm; use Icinga\Forms\Config\UserGroup\UserGroupForm; @@ -17,7 +18,7 @@ use Icinga\Web\Form; use Icinga\Web\Notification; use Icinga\Web\Url; -use Icinga\Web\Widget; +use ipl\Web\Url as IplUrl; class GroupController extends AuthBackendController { @@ -224,24 +225,50 @@ public function addmemberAction() { $this->assertPermission('config/access-control/groups'); $groupName = $this->params->getRequired('group'); - $backend = $this->getUserGroupBackend($this->params->getRequired('backend'), 'Icinga\Data\Extensible'); + $backend = $this->getUserGroupBackend( + $this->params->getRequired('backend'), + 'Icinga\Data\Extensible' + ); - $form = new AddMemberForm(); - $form->setDataSource($this->fetchUsers()) + $form = (new AddMemberForm()) ->setBackend($backend) ->setGroupName($groupName) ->setRedirectUrl( - Url::fromPath('group/show', array('backend' => $backend->getName(), 'group' => $groupName)) + IplUrl::fromPath('group/show', ['backend' => $backend->getName(), 'group' => $groupName]) ) - ->setUidDisabled(); + ->setSuggestionUrl(IplUrl::fromPath( + 'group/complete', + [ + '_disableLayout' => true, + 'showCompact' => true, + 'backend' => $this->params->getRequired('backend'), + 'group' => $groupName + ] + )); - try { - $form->handleRequest(); - } catch (NotFoundError $_) { - $this->httpNotFound(sprintf($this->translate('Group "%s" not found'), $groupName)); - } + $form->on(AddMemberForm::ON_SUCCESS, function ($form) { + $this->getResponse()->redirectAndExit($form->getRedirectUrl()); + })->handleRequest($this->getServerRequest()); + + $this->addTitleTab($this->translate('New User Group Member')); + $this->addContent($form); + } + + public function completeAction() + { + $backend = $this->getUserGroupBackend( + $this->params->getRequired('backend'), + 'Icinga\Data\Extensible' + ); + + $groupName = $this->params->getRequired('group'); - $this->renderForm($form, $this->translate('New User Group Member')); + $suggestions = (new UserSuggestions()) + ->setUserGroupName($groupName) + ->setUserGroupBackend($backend) + ->setUserBackends($this->loadUserBackends('Icinga\Data\Selectable')) + ->forRequest($this->getServerRequest()); + $this->getDocument()->addHtml($suggestions); } /** diff --git a/application/forms/Config/UserGroup/AddMemberForm.php b/application/forms/Config/UserGroup/AddMemberForm.php index debb9b7af0..c981f12771 100644 --- a/application/forms/Config/UserGroup/AddMemberForm.php +++ b/application/forms/Config/UserGroup/AddMemberForm.php @@ -1,28 +1,18 @@ ds = $ds; - return $this; - } - /** * Set the user group backend to use * @@ -76,74 +53,18 @@ public function setGroupName($groupName) return $this; } - /** - * Create and add elements to this form - * - * @param array $formData The data sent by the user - */ - public function createElements(array $formData) - { - // TODO(jom): Fetching already existing members to prevent the user from mistakenly creating duplicate - // memberships (no matter whether the data source permits it or not, a member does never need to be - // added more than once) should be kept at backend level (GroupController::fetchUsers) but this does - // not work currently as our ldap protocol stuff is unable to handle our filter implementation.. - $members = $this->backend - ->select() - ->from('group_membership', array('user_name')) - ->where('group_name', $this->groupName) - ->fetchColumn(); - $filter = empty($members) ? Filter::matchAll() : Filter::not(Filter::where('user_name', $members)); - - $users = $this->ds->select()->from('user', array('user_name'))->applyFilter($filter)->fetchColumn(); - if (! empty($users)) { - $this->addElement( - 'multiselect', - 'user_name', - array( - 'multiOptions' => array_combine($users, $users), - 'label' => $this->translate('Backend Users'), - 'description' => $this->translate( - 'Select one or more users (fetched from your user backends) to add as group member' - ), - 'class' => 'grant-permissions' - ) - ); - } - - $this->addElement( - 'textarea', - 'users', - array( - 'required' => empty($users), - 'label' => $this->translate('Users'), - 'description' => $this->translate( - 'Provide one or more usernames separated by comma to add as group member' - ) - ) - ); - - $this->setTitle(sprintf($this->translate('Add members for group %s'), $this->groupName)); - $this->setSubmitLabel($this->translate('Add')); - } - - /** - * Insert the members for the group - * - * @return bool - */ public function onSuccess() { - $userNames = $this->getValue('user_name') ?: array(); - if (($users = $this->getValue('users'))) { - $userNames = array_merge($userNames, array_map('trim', explode(',', $users))); - } + $q = $this->getValue($this->getSearchParameter(), ''); + + $userNames = array_unique(array_filter( + array_map('trim', explode(self::TERM_SEPARATOR, rawurldecode($q))) + )); if (empty($userNames)) { - $this->info($this->translate( - 'Please provide at least one username, either by choosing one ' - . 'in the list or by manually typing one in the text box below' - )); - return false; + Notification::info(t('Please provide at least one username')); + + return; } $single = null; @@ -151,32 +72,28 @@ public function onSuccess() try { $this->backend->insert( 'group_membership', - array( + [ 'group_name' => $this->groupName, 'user_name' => $userName - ) + ] ); - } catch (NotFoundError $e) { - throw $e; // Trigger 404, the group name is initially accessed as GET parameter } catch (Exception $e) { Notification::error(sprintf( - $this->translate('Failed to add "%s" as group member for "%s"'), + t('Failed to add "%s" as group member for "%s"'), $userName, $this->groupName )); - $this->error($e->getMessage()); - return false; + + return; } $single = $single === null; } if ($single) { - Notification::success(sprintf($this->translate('Group member "%s" added successfully'), $userName)); + Notification::success(sprintf(t('Group member "%s" added successfully'), $userName)); } else { - Notification::success($this->translate('Group members added successfully')); + Notification::success(t('Group members added successfully')); } - - return true; } }