From 69c66adb93546fad567bfdec9e835f272ea1fef2 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Thu, 15 Feb 2024 15:11:34 +0000 Subject: [PATCH 01/60] Refactoring backend.Naming conventions.CAKEPHPism and COmanage conform to conventions. --- Config/Schema/empty | 0 Config/Schema/schema.xml | 5 +- ...php => CoGrouperLiteWidgetsController.php} | 33 +- Controller/GrouperGroupsController.php | 624 +++++++----------- Controller/GrouperLiteAppController.php | 7 - Controller/GrouperLiteWidgetAppController.php | 7 + Lib/GrouperApiAccess.php | 432 ++++++------ Lib/GrouperHTTPWrapper.php | 6 +- ...ion.php => GrouperLiteWidgetException.php} | 4 +- Lib/empty | 0 Lib/enum.php | 9 + Lib/lang.php | 8 +- Model/CoGrouperLite.php | 98 --- Model/CoGrouperLiteWidget.php | 152 +++++ Model/CoManagePerson.php | 2 +- Model/GrouperAttribute.php | 12 +- Model/GrouperGroup.php | 154 ++--- ...{GrouperLite.php => GrouperLiteWidget.php} | 25 +- ...odel.php => GrouperLiteWidgetAppModel.php} | 2 +- .../display.ctp | 25 +- .../edit.ctp | 0 .../fields.inc | 87 ++- View/Elements/Components/vue-table.ctp | 84 +-- View/GrouperGroups/base.ctp | 11 +- View/GrouperGroups/groupmember.ctp | 2 +- View/GrouperGroups/groupoptin.ctp | 6 +- View/GrouperGroups/groupowner.ctp | 6 +- View/GrouperGroups/index.ctp | 82 +-- View/Layouts/error.ctp | 6 +- 29 files changed, 906 insertions(+), 983 deletions(-) delete mode 100644 Config/Schema/empty rename Controller/{CoGrouperLitesController.php => CoGrouperLiteWidgetsController.php} (70%) delete mode 100644 Controller/GrouperLiteAppController.php create mode 100644 Controller/GrouperLiteWidgetAppController.php rename Lib/{GrouperLiteException.php => GrouperLiteWidgetException.php} (94%) delete mode 100644 Lib/empty create mode 100644 Lib/enum.php delete mode 100644 Model/CoGrouperLite.php create mode 100644 Model/CoGrouperLiteWidget.php rename Model/{GrouperLite.php => GrouperLiteWidget.php} (79%) rename Model/{GrouperLiteAppModel.php => GrouperLiteWidgetAppModel.php} (95%) rename View/{CoGrouperLites => CoGrouperLiteWidgets}/display.ctp (87%) rename View/{CoGrouperLites => CoGrouperLiteWidgets}/edit.ctp (100%) rename View/{CoGrouperLites => CoGrouperLiteWidgets}/fields.inc (72%) diff --git a/Config/Schema/empty b/Config/Schema/empty deleted file mode 100644 index e69de29..0000000 diff --git a/Config/Schema/schema.xml b/Config/Schema/schema.xml index 1e39027..f2bfb51 100644 --- a/Config/Schema/schema.xml +++ b/Config/Schema/schema.xml @@ -25,7 +25,7 @@ must be specified in raw SQL, which needs the prefixed table name. --> - +
@@ -34,6 +34,7 @@ REFERENCES cm_co_dashboard_widgets(id) + @@ -44,7 +45,7 @@ - + co_dashboard_widget_id diff --git a/Controller/CoGrouperLitesController.php b/Controller/CoGrouperLiteWidgetsController.php similarity index 70% rename from Controller/CoGrouperLitesController.php rename to Controller/CoGrouperLiteWidgetsController.php index ccbaa42..1ea93f0 100644 --- a/Controller/CoGrouperLitesController.php +++ b/Controller/CoGrouperLiteWidgetsController.php @@ -2,16 +2,18 @@ App::uses("SDWController", "Controller"); -class CoGrouperLitesController extends SDWController { +class CoGrouperLiteWidgetsController extends SDWController { public $helpers = array('Html', 'Form', 'Flash'); public $components = array('Flash'); // Class name, used by Cake - public $name = "CoGrouperLites"; + public $name = "CoGrouperLiteWidgets"; public $uses = array( - "GrouperLite.CoGrouperLite" + 'GrouperLiteWidget.CoGrouperLiteWidget', + 'Identifier', + 'Co', ); /** @@ -21,13 +23,26 @@ class CoGrouperLitesController extends SDWController { */ function beforeRender() { + parent::beforeRender(); + $this->set('title_for_layout', _txt('pl.grouperlite.config.edit.title')); - parent::beforeRender(); + // Gather the available identifier address types for the config form + $this->set('vv_available_types', $this->Identifier->types($this->cur_co['Co']['id'], 'type')); + // Pass the config + $cfg = $this->CoGrouperLiteWidget->getConfig(); + $this->set('vv_config', $cfg); } + + /** + * Render the widget according to the requested user and current configuration. + * + * @since COmanage Registry v4.4.0 + * @param Integer $id CO Grouper Lite Widget ID + */ public function display($id) { - $cfg = $this->CoGrouperLite->getConfig(); + $cfg = $this->CoGrouperLiteWidget->getConfig(); $this->set('pl_grouperlite_index_url', Router::url([ 'plugin' => "grouper_lite", @@ -43,6 +58,14 @@ public function display($id) { // Pass the config so we know which div to overwrite $this->set('vv_config', $cfg); + + // Pass the Dashboard Widget configuration + $args = array(); + $args['conditions']['CoGrouperLiteWidget.id'] = $id; + $args['contain']['CoDashboardWidget'][] = 'CoDashboard'; + + $codw = $this->CoGrouperLiteWidget->find('first', $args); + $this->set('vv_codw', $codw); } diff --git a/Controller/GrouperGroupsController.php b/Controller/GrouperGroupsController.php index 03f0146..d0379bd 100644 --- a/Controller/GrouperGroupsController.php +++ b/Controller/GrouperGroupsController.php @@ -26,8 +26,8 @@ */ App::uses('Validator', 'Vendor/cakephp/Validation'); -App::uses('CoGrouperLite', 'GrouperLite.Model/'); -App::uses('GrouperGroup', 'GrouperLite.Model/'); +App::uses('CoGrouperLite', 'GrouperLiteWidget.Model/'); +App::uses('GrouperGroup', 'GrouperLiteWidget.Model/'); App::uses('Identifier', 'Model'); /** @@ -35,10 +35,19 @@ * Main Class for Grouper Lite functionality * */ -class GrouperGroupsController extends GrouperLiteAppController +class GrouperGroupsController extends GrouperLiteWidgetAppController { public $helpers = array('Html', 'Form', 'Flash'); - public $uses = array('GrouperLite.GrouperGroup', 'CoPerson'); + + // Dynamic properties are deprecated so we will define the property here + protected $userId = null; + + public $uses = array( + 'GrouperLiteWidget.GrouperGroup', + 'GrouperLiteWidget.CoGrouperLiteWidget', + 'Identifier', + 'CoPerson'); + public $components = array('Flash', 'Paginator', 'RequestHandler', 'Security' => array( 'validatePost' => false, 'csrfUseOnce' => false @@ -56,6 +65,11 @@ public function beforeFilter() { parent::beforeFilter(); + if(empty($this->request->params['named']['glid'])) { + throw new InvalidArgumentException(_txt('er.grouperlite.glid'), + HttpStatusCodesEnum::HTTP_BAD_REQUEST); + } + $this->Security->unlockedActions = array( 'removeSubscriber', 'addSubscriber', @@ -66,70 +80,37 @@ public function beforeFilter() 'groupOwner' ); - //Need to find which plugin instance choosing, if more than one from cm_co_grouper_lites - // table being used in COmanage. - $grouperConnData = $this->Session->read('Plugin.Grouper.Api'); - if ($this->Session->check('Plugin.Grouper.Api.id') && count($grouperConnData) == 10) { - if (isset($this->passedArgs['glid'])) { - if ($this->Session->read('Plugin.Grouper.Api.id') !== $this->passedArgs['glid']) { - $this->setConnection(); - } - } - } else { - if (!isset($this->passedArgs['glid'])) { - //If user links directly to GrouperLite url, will be redirected to main index to choose an - //appropriate Dashboard Widget. - return $this->redirect('/'); - } - $this->setConnection(); + if ($this->request->is('ajax')) { + $this->response->disableCache(); + $this->RequestHandler->addInputType('json', array('json_decode', true)); } - //Also check for CO of Organization - if (!$this->Session->check('Plugin.Grouper.Api.co')) { - $this->Session->write('Plugin.Grouper.Api.co', $this->passedArgs['co']); - } + // Get the config + $args = array(); + $args['conditions']['CoGrouperLiteWidget.id'] = $this->request->params["named"]["glid"]; + $args['contain'] = false; + $cfg = $this->CoGrouperLiteWidget->find('first', $args); + // Set the config so that everybody can access it + $this->CoGrouperLiteWidget->setConfig($cfg); } /** - * Adding Grouper Conn info to SESSION for use in Lib/GrouperApiAccess.php + * Callback after controller methods are invoked but before views are rendered. + * - precondition: Request Handler component has set $this->request + * + * @since COmanage Registry v4.4.0 */ - private function setConnection() - { - $this->Session->write('Plugin.Grouper.Api.id', $this->passedArgs['glid']); - $this->Session->write('Plugin.Grouper.Api.co', $this->passedArgs['co']); - - //Now get the setup Dasboard instance from db for connection info. - $getConnInfo = new CoGrouperLite(); - $connectionInfo = $getConnInfo->findById($this->passedArgs['glid']); - $this->Session->write('Plugin.Grouper.Api.url', $connectionInfo['CoGrouperLite']['conn_url']); - $this->Session->write('Plugin.Grouper.Api.version', $connectionInfo['CoGrouperLite']['conn_ver']); - $this->Session->write('Plugin.Grouper.Api.user', $connectionInfo['CoGrouperLite']['conn_user']); - $this->Session->write('Plugin.Grouper.Api.pass', $connectionInfo['CoGrouperLite']['conn_pass']); - $this->Session->write('Plugin.Grouper.Api.grouperUrl', $connectionInfo['CoGrouperLite']['grouper_url']); - - $this->Session->write('Plugin.Grouper.Api.adHocHeading', $connectionInfo['CoGrouperLite']['adhoc_heading']); - $this->Session->write('Plugin.Grouper.Api.wgHeading', $connectionInfo['CoGrouperLite']['wg_heading']); - $this->Session->write('Plugin.Grouper.Api.defaultCollapse', $connectionInfo['CoGrouperLite']['default_collapse']); - } - private function getConfig($page = 'groupmember') - { - $this->set('title', _txt('pl.grouperlite.title.groupmember')); + public function beforeRender() { + parent::beforeRender(); + $cfg = $this->CoGrouperLiteWidget->getConfig(); + $this->set('vv_config', $cfg); - $config = [ - "grouperbaseurl" => $this->Session->read('Plugin.Grouper.Api.grouperUrl'), - "isuserowner" => $this->GrouperGroup->isUserOwner($this->userId), - "isTemplateUser" => $this->GrouperGroup->isTemplateUser($this->userId), - "isGrouperVisible" => $this->GrouperGroup->isGrouperVisible($this->userId), - "defaultCollapse" => CakeSession::read('Plugin.Grouper.Api.defaultCollapse'), - "adHocHeading" => CakeSession::read('Plugin.Grouper.Api.adHocHeading'), - "wgHeading" => CakeSession::read('Plugin.Grouper.Api.wgHeading'), - 'co' => CakeSession::read('Plugin.Grouper.Api.co'), - 'glid' => $this->Session->read('Plugin.Grouper.Api.id'), - 'view' => $page - ]; - - return $config; + $this->set('title', _txt('pl.grouperlite.title.groupmember')); + $this->set('vv_is_user_owner', $this->GrouperGroup->isUserOwner($this->userId ?? '', $cfg) ); + $this->set('vv_is_template_user', $this->GrouperGroup->isTemplateUser($this->userId ?? '', $cfg) ); + $this->set('vv_is_grouper_visible', $this->GrouperGroup->isGrouperVisible($this->userId ?? '', $cfg)); + $this->set('vv_coid', $this->cur_co['Co']['id']); } /** @@ -137,26 +118,29 @@ private function getConfig($page = 'groupmember') * * @return CakeResponse Redirect to MyMembership page */ - public function index() - { - $this->set('config', $this->getConfig('groupmember')); - } + public function index() {} + /** + * @return void + */ public function groupMember() { - $this->set('config', $this->getConfig('groupmember')); $this->render('index'); } + /** + * @return void + */ public function groupOptin() { - $this->set('config', $this->getConfig('groupoptin')); $this->render('index'); } + /** + * @return void + */ public function groupOwner() { - $this->set('config', $this->getConfig('groupowner')); $this->render('index'); } @@ -168,25 +152,19 @@ public function groupOwner() public function groupSubscribers() { $groupName = urldecode($this->request->query['groupname']); - - if ($this->request->is('ajax')) { - $this->response->disableCache(); - } + $subscribers = 0; //Need to see if coming from AdHoc or from a WG (Working Group) - if (strpos($groupName, ':') === false) { - $groupNameFormatted = 'ref:incommon-collab:' . $groupName . ':users'; - } else { - $groupNameFormatted = $groupName; - } - + $groupNameFormatted = strpos($groupName, ':') === false ? 'ref:incommon-collab:' . $groupName . ':users' + : $groupName; //Set initial $scope = [ 'groupName' => $groupNameFormatted ]; try { - $subscribers = $this->GrouperGroup->membersInGroup($scope, $this->userId); + $subscribers = $this->GrouperGroup->membersInGroup($scope, $this->userId, $this->CoGrouperLiteWidget->getConfig()); + CakeLog::write('debug', __METHOD__ . '::response: ' . var_export($subscribers, true)); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': ' . var_export($e->getMessage(), true)); @@ -195,21 +173,14 @@ public function groupSubscribers() } if(count($subscribers) < 1){ - $this->response->type('json'); - $this->response->statusCode(404); - //$this->response->body(json_encode(array('status' => 'ERROR', 'message' => 'NO ACCESS'))); - $this->response->send(); - $subscribers = ''; - } elseif (count($subscribers) == 1 && $subscribers[0]['sourceId'] == "NoAccess") { - $this->response->type('json'); - $this->response->statusCode(403); - //$this->response->body(json_encode(array('status' => 'ERROR', 'message' => 'NO ACCESS'))); - $this->response->send(); - $subscribers = ''; + $this->restResponse(HttpStatusCodesEnum::HTTP_NOT_FOUND, ErrorsEnum::Error); + } elseif (count($subscribers) == 1 + && $subscribers[0]['sourceId'] === 'NoAccess') { + $this->restResponse(HttpStatusCodesEnum::HTTP_FORBIDDEN, ErrorsEnum::NoAccess); } - $this->set(compact('subscribers')); - $this->set('_serialize', 'subscribers'); + $this->set(compact('subscribers')); + $this->set('_serialize', 'subscribers'); } /** @@ -217,22 +188,14 @@ public function groupSubscribers() * Called from all pages via AJAX call * */ - public function addSubscriber() - { + public function addSubscriber() { $groupName = urldecode($this->request->query['group']); $addUserId = urldecode($this->request->query['userId']); - - if ($this->request->is('ajax')) { - $this->response->disableCache(); - } + $resultAdd = ''; //Need to see if coming from AdHoc or from a WG (Working Group) - if (strpos($groupName, ':') === false) { - $groupNameFormatted = 'ref:incommon-collab:' . $groupName . ':users'; - } else { - $groupNameFormatted = $groupName; - } - + $groupNameFormatted = strpos($groupName, ':') === false ? 'ref:incommon-collab:' . $groupName . ':users' + : $groupName; //Set initial $scope = [ 'groupName' => $groupNameFormatted, @@ -240,30 +203,18 @@ public function addSubscriber() ]; try { - $resultAdd = $this->GrouperGroup->addMemberToGroup($scope, $this->userId); - + $resultAdd = $this->GrouperGroup->addMemberToGroup($scope, + $this->userId, $this->CoGrouperLiteWidget->getConfig()); + CakeLog::write('debug', __METHOD__ . '::response: ' . var_export($resultAdd, true)); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': ' . var_export($e->getMessage(), true)); - $subscribers = 'ERROR'; - - // $this->Flash->set(_txt('pl.grouperlite.message.flash.group-detail-members-failed'), array('key' => 'error')); } - if ($resultAdd == 'SUCCESS') { - // Do nothing - } elseif ($resultAdd == 'EXCEPTION') { - $this->response->type('json'); - $this->response->statusCode(404); - $this->response->body(json_encode(array('status' => 'ERROR', 'message' => 'EXCEPTION'))); - $this->response->send(); - $resultAdd = ''; - } else { - $this->response->type('json'); - $this->response->statusCode(401); - $this->response->body(json_encode(array('status' => 'ERROR', 'message' => 'ERROR'))); - $this->response->send(); + if ($resultAdd !== 'SUCCESS') { + $this->restResponse(HttpStatusCodesEnum::HTTP_UNAUTHORIZED, ErrorsEnum::Error); $resultAdd = ''; } + $this->set(compact('resultAdd')); $this->set('_serialize', 'resultAdd'); @@ -275,14 +226,7 @@ public function addSubscriber() * to work. * */ - public function findSubscriber() - { - - if ($this->request->is('ajax')) { - $this->response->disableCache(); - } - - // $groupName = urldecode($this->request->query['group']); + public function findSubscriber() { $findUserId = urldecode($this->request->query['term']); $co = urldecode($this->request->query['co']); @@ -390,22 +334,14 @@ public function findSubscriber() * Called from all pages via AJAX call * */ - public function removeSubscriber() - { + public function removeSubscriber() { $groupName = urldecode($this->request->query['group']); $remUserId = urldecode($this->request->query['userId']); - - if ($this->request->is('ajax')) { - $this->response->disableCache(); - } + $resultRemove = ''; //Need to see if coming from AdHoc or from a WG (Working Group) - if (strpos($groupName, ':') === false) { - $groupNameFormatted = 'ref:incommon-collab:' . $groupName . ':users'; - } else { - $groupNameFormatted = $groupName; - } - + $groupNameFormatted = (strpos($groupName, ':') === false) ? 'ref:incommon-collab:' . $groupName . ':users' + : $groupName; //Set initial $scope = [ 'groupName' => $groupNameFormatted, @@ -413,23 +349,20 @@ public function removeSubscriber() ]; try { - $resultRemove = $this->GrouperGroup->removeMemberToGroup($scope, $this->userId); + $resultRemove = $this->GrouperGroup->removeMemberToGroup($scope, + $this->userId, + $this->CoGrouperLiteWidget->getConfig()); + CakeLog::write('debug', __METHOD__ . '::response: ' . var_export($resultRemove, true)); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': ' . var_export($e->getMessage(), true)); - - //$this->Flash->set(_txt('pl.grouperlite.message.flash.group-detail-members-failed'), array('key' => 'error')); } - if ($resultRemove == 'SUCCESS') { - // Do nothing - } else { - $this->response->type('json'); - $this->response->statusCode(404); - $this->response->body(json_encode(array('status' => 'ERROR', 'message' => 'ERROR'))); - $this->response->send(); + if ($resultRemove != 'SUCCESS') { + $this->restResponse(HttpStatusCodesEnum::HTTP_NOT_FOUND, ErrorsEnum::Error); $resultRemove = ''; } + $this->set(compact('resultRemove')); $this->set('_serialize', 'resultRemove'); } @@ -437,12 +370,7 @@ public function removeSubscriber() /** * Listing of all Grouper Groups owned/admin by User Or search those Grouper Groups */ - public function groupOwnerApi() - { - if ($this->request->is('ajax')) { - $this->response->disableCache(); - } - + public function groupOwnerApi() { //Set initial setting $scope = [ 'userId' => $this->userId @@ -450,44 +378,29 @@ public function groupOwnerApi() if (isset($this->request->query['search'])) { $searchCriteria = urldecode($this->request->query['search']); - $this->set('searchcriteria', $searchCriteria); - - try { - //Add settings for search Owned Groups - $scope['method'] = 'getSearchedGroups'; - $scope['searchcriteria'] = $searchCriteria; - $scope['searchpage'] = 'ownerGroups'; - - $groupowners = $this->GrouperGroup->getSearchedGroups($scope); - - } catch (Exception $e) { - CakeLog::write('error', __METHOD__ . ' Search: ' . var_export($e->getMessage(), true)); - $this->response->type('json'); - $this->response->statusCode(500); - $this->response->send(); - $this->set('groupsowners', array()); - - $this->Flash->set(_txt('pl.grouperlite.message.flash.owner-group-failed'), array('key' => 'error')); - return; - } + //Add settings for search Owned Groups + $scope['method'] = 'getSearchedGroups'; + $scope['searchcriteria'] = $searchCriteria; + $scope['searchpage'] = 'ownerGroups'; + $errorHint = 'Search'; } else { - try { - $scope['method'] = 'ownerGroups'; - - $groupowners = $this->GrouperGroup->ownerGroups($scope); - - } catch (Exception $e) { - CakeLog::write('error', __METHOD__ . ': ' . var_export($e->getMessage(), true)); - $this->response->type('json'); - $this->response->statusCode(500); - $this->response->send(); - $this->set('groupsowners', array()); + $scope['method'] = 'ownerGroups'; + $errorHint = ''; + } + try { + $func = $scope['method']; + $groupowners = $this->GrouperGroup->$func($scope, $this->CoGrouperLiteWidget->getConfig()); + CakeLog::write('debug', __METHOD__ . "::response: " . var_export($groupowners, true)); + } catch (Exception $e) { + CakeLog::write('error', __METHOD__ . "::{$errorHint}: " . var_export($e->getMessage(), true)); + $this->restResponse(HttpStatusCodesEnum::HTTP_INTERNAL_SERVER_ERROR, ErrorsEnum::Exception); - $this->Flash->set(_txt('pl.grouperlite.message.flash.owner-group-failed'), array('key' => 'error')); - return; - } + $this->set('groupsowners', array()); + $this->Flash->set(_txt('pl.grouperlite.message.flash.owner-group-failed'), array('key' => 'error')); + return; } + $this->set(compact('groupowners')); $this->set('_serialize', 'groupowners'); } @@ -497,12 +410,7 @@ public function groupOwnerApi() * This includes self-joined Optin Groups, as well as required Groups User cannot leave * */ - public function groupMemberApi() - { - if ($this->request->is('ajax')) { - $this->response->disableCache(); - } - + public function groupMemberApi() { //Set initial setting $scope = [ 'userId' => $this->userId @@ -510,66 +418,44 @@ public function groupMemberApi() if (isset($this->request->query['search'])) { $searchCriteria = urldecode($this->request->query['search']); - $this->set('searchcriteria', $searchCriteria); - try { - //Add settings for search Member Groups - $scope['method'] = 'getSearchedGroups'; - $scope['searchcriteria'] = $searchCriteria; - $scope['searchpage'] = 'filteredMemberOfGroups'; - $scope['ContainsWG'] = true; - - $data = $this->GrouperGroup->getSearchedGroups($scope); - - $finalData = $this->breakoutGroups($data); - - } catch (Exception $e) { - CakeLog::write('error', __METHOD__ . ' Search: ' . var_export($e->getMessage(), true)); - $this->response->type('json'); - $this->response->statusCode(500); - $this->response->send(); - $this->set('groupmemberships', array()); - $this->set('wgmemberships', array()); - - $this->Flash->set("Your Search Group cannot be found, please try again later.", array('key' => 'error')); - return; - } + //Add settings for search Member Groups + $scope['method'] = 'getSearchedGroups'; + $scope['searchcriteria'] = $searchCriteria; + $scope['searchpage'] = 'filteredMemberOfGroups'; + $scope['ContainsWG'] = true; + $errorHint = 'Search'; } else { - try { - //Add setting for Group Membership - $scope['method'] = 'filteredMemberOfGroups'; + //Add setting for Group Membership + $scope['method'] = 'filteredMemberOfGroups'; + $errorHint = ''; + } - $data = $this->GrouperGroup->filteredMemberOfGroups($scope); + try { + $func = $scope['method']; + $data = $this->GrouperGroup->$func($scope, + $this->CoGrouperLiteWidget->getConfig()); + $finalData = $this->breakoutGroups($data); + } catch (Exception $e) { + CakeLog::write('error', __METHOD__ . "::{$errorHint}: " . var_export($e->getMessage(), true)); - $finalData = $this->breakoutGroups($data); + $this->restResponse(HttpStatusCodesEnum::HTTP_INTERNAL_SERVER_ERROR, ErrorsEnum::Exception); + $this->set('groupmemberships', []); + $this->set('wgmemberships', []); - } catch (Exception $e) { - CakeLog::write('error', __METHOD__ . ': ' . var_export($e->getMessage(), true)); - $this->response->type('json'); - $this->response->statusCode(500); - $this->response->send(); - $this->set('groupmemberships', array()); - $this->set('wgmemberships', array()); - - $this->Flash->set("Your Member Group cannot be found, please try again later.", array('key' => 'error')); - return; - } + $this->Flash->set('Your Member Group cannot be found, please try again later.', array('key' => 'error')); + return; } - $this->set(compact('finalData')); - $this->set('_serialize', 'finalData'); + $this->set(compact('finalData')); + $this->set('_serialize', 'finalData'); } /** * Display all Groups a User can Join */ - public function groupOptinApi() - { - if ($this->request->is('ajax')) { - $this->response->disableCache(); - } - + public function groupOptinApi() { //Set initial setting $scope = [ 'userId' => $this->userId @@ -577,45 +463,30 @@ public function groupOptinApi() if (isset($this->request->query['search'])) { $searchCriteria = urldecode($this->request->query['search']); - $this->set('searchcriteria', $searchCriteria); + //Add settings for search Optin's + $scope['method'] = 'getSearchedGroups'; + $scope['searchcriteria'] = $searchCriteria; + $scope['searchpage'] = 'optinGroups'; + $errorHint = 'Search'; - try { - //Add settings for search Optin's - $scope['method'] = 'getSearchedGroups'; - $scope['searchcriteria'] = $searchCriteria; - $scope['searchpage'] = 'optinGroups'; - - $groupoptins = $this->GrouperGroup->getSearchedGroups($scope); - - } catch (Exception $e) { - CakeLog::write('error', __METHOD__ . 'Search: ' . var_export($e->getMessage(), true)); - $this->response->type('json'); - $this->response->statusCode(500); - $this->response->send(); - $this->set('groupoptins', array()); - - $this->Flash->set("Your Optin Group Search cannot be found, please try again later.", array('key' => 'error')); - return; - } } else { - try { - //Add settings for optinGroups - $scope['method'] = 'optinGroups'; - - $groupoptins = $this->GrouperGroup->optinGroups($scope); - - } catch (Exception $e) { - CakeLog::write('error', __METHOD__ . ': ' . var_export($e->getMessage(), true)); - $this->response->type('json'); - $this->response->statusCode(500); - $this->response->send(); - $this->set('groupoptins', array()); + $scope['method'] = 'optinGroups'; + $errorHint = ''; + } + try { + $func = $scope['method']; + $groupoptins = $this->GrouperGroup->$func($scope, + $this->CoGrouperLiteWidget->getConfig()); + } catch (Exception $e) { + CakeLog::write('error', __METHOD__ . "::{$errorHint}: " . var_export($e->getMessage(), true)); + $this->restResponse(HttpStatusCodesEnum::HTTP_INTERNAL_SERVER_ERROR, ErrorsEnum::Exception); + $this->set('groupoptins', array()); - $this->Flash->set("An error occurred with the Optin Groups, please try again later.", array('key' => 'error')); - return; - } + $this->Flash->set('An error occurred with the Optin Groups, please try again later.', array('key' => 'error')); + return; } + $this->set(compact('groupoptins')); $this->set('_serialize', 'groupoptins'); } @@ -626,14 +497,14 @@ public function groupOptinApi() * Note: This is tightly coupled code to requirements, so view is hardcoded to reflect current reqs. Will need * to update when reqs change or are updated!!! * - * Editing via a template will not be supported in this version of the plugin - Bill Kaufman - * */ public function groupCreateTemplate() { if ($this->request->is('post')) { try { - $status = $this->GrouperGroup->createGroupWithTemplate($this->userId, $this->request->data); + $status = $this->GrouperGroup->createGroupWithTemplate($this->userId, + $this->request->data, + $this->CoGrouperLiteWidget->getConfig()); if ($status['status'] !== true) { $this->Flash->set($status['message'], array('key' => 'error')); @@ -658,33 +529,18 @@ public function groupCreateTemplate() */ public function joinGroup() { + $resultAdd = 'Success'; $name = urldecode($this->request->query['GroupName']); $display = urldecode($this->request->query['GroupDisplayName']); - if ($this->request->is('ajax')) { - $this->response->disableCache(); - } - try { - if ($this->GrouperGroup->joinGroup($this->userId, $name)) { - /*$this->Flash->set( - _txt('pl.grouperlite.message.flash.join-group-success', array(filter_var($display, FILTER_SANITIZE_SPECIAL_CHARS))), - array('key' => 'success') - );*/ - $resultAdd = "Success"; - } else { - $this->response->type('json'); - $this->response->statusCode(401); - $this->response->body(json_encode(array('status' => 'ERROR', 'message' => 'NOT ADDED'))); - $this->response->send(); + if (!$this->GrouperGroup->joinGroup($this->userId, $name)) { + $this->restResponse(HttpStatusCodesEnum::HTTP_UNAUTHORIZED, ErrorsEnum::NotAdded); $resultAdd = ''; } } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': ' . var_export($e->getMessage(), true)); - $this->response->type('json'); - $this->response->statusCode(404); - $this->response->body(json_encode(array('status' => 'ERROR', 'message' => 'EXCEPTION'))); - $this->response->send(); + $this->restResponse(HttpStatusCodesEnum::HTTP_NOT_FOUND, ErrorsEnum::Exception); $resultAdd = ''; } @@ -701,27 +557,18 @@ public function leaveGroup() { $name = urldecode($this->request->query['GroupName']); $display = urldecode($this->request->query['GroupDisplayName']); - - if ($this->request->is('ajax')) { - $this->response->disableCache(); - } + $resultRemove = 'Success'; try { - if ($this->GrouperGroup->leaveGroup($this->userId, $name)) { - $resultRemove = "Success"; - } else { - $this->response->type('json'); - $this->response->statusCode(401); - $this->response->body(json_encode(array('status' => 'ERROR', 'message' => 'NOT DELETED'))); - $this->response->send(); + if (!$this->GrouperGroup->leaveGroup($this->userId, + $name, + $this->CoGrouperLiteWidget->getConfig())) { + $this->restResponse(HttpStatusCodesEnum::HTTP_UNAUTHORIZED, ErrorsEnum::NotDeleted); $resultRemove = ''; } } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': ' . var_export($e->getMessage(), true)); - $this->response->type('json'); - $this->response->statusCode(404); - $this->response->body(json_encode(array('status' => 'ERROR', 'message' => 'EXCEPTION'))); - $this->response->send(); + $this->restResponse(HttpStatusCodesEnum::HTTP_NOT_FOUND, ErrorsEnum::Exception); $resultRemove = ''; } @@ -744,44 +591,22 @@ function isAuthorized() { $roles = $this->Role->calculateCMRoles(); - /** - * The following code displays a few custom implementations of the - * login process used to crosswalk a user for Grouper authentication. - * - * You may need to further customize this section to meet your organization - * crosswalk needs. - */ - - /** - * Default when login-id is the same as grouper id - */ - // Default Begin =============================================== - - /* - if ($this->Session->check('Auth.User.username')) { - $this->userId = $this->Session->read('Auth.User.username'); - } - */ - // Default End =============================================== - - /** - * Customized Crosswalk from login-id to Grouper Username - */ - // Custom Begin =============================================== - - $username = $this->Session->read('Auth.User.username'); + // Find the identifier + $args = array(); + // XXX This should become configuration + $args['conditions']['Identifier.type'] = 'I2CollabPN'; + $args['conditions']['Identifier.status'] = SuspendableStatusEnum::Active; + $args['conditions']['Identifier.co_person_id'] = $roles["copersonid"]; + $args['contain'] = false; - if ($this->Session->check('Plugin.Grouper.UserId')) { - $this->userId = $this->Session->read('Plugin.Grouper.UserId'); - } else { - $uid = $this->getPersonIdFromUsername($username); - $this->userId = $this->getUserId($uid); - $this->Session->write('Plugin.Grouper.UserId', $this->userId); + $identifiers = $this->Identifier->find('first', $args); + if(!empty($identifiers) + && is_array($identifiers) + && isset($identifiers["Identifier"]["identifier"]) + ) { + $this->userId = $identifiers["Identifier"]["identifier"]; } - // Custom End =============================================== - - // Determine what operations this user can perform // Construct the permission set for this user, which will also be passed to the view. //Note: Leaving in current format, in case need to restrict certain pages, can just remove true and add params. @@ -810,48 +635,15 @@ function isAuthorized() return ($p[$this->action]); } - private function getPersonIdFromUsername($username) - { - $args = array(); - $args['conditions']['Identifier.identifier'] = $username; - $args['conditions']['Identifier.status'] = SuspendableStatusEnum::Active; - $args['conditions']['Identifier.deleted'] = false; - $args['conditions']['Identifier.identifier_id'] = null; - $args['conditions']['NOT']['Identifier.co_person_id'] = null; - $args['conditions']['Identifier.type'] = 'eppn'; - $args['contain'] = false; - - $Identifier = new Identifier(); - $co_person_id = $Identifier->find('first', $args); - - return $co_person_id['Identifier']['co_person_id']; - } - - private function getUserId($id) - { - $args = array(); - $args['conditions']['Identifier.co_person_id'] = $id; - $args['conditions']['Identifier.type'] = 'I2CollabPN'; - $args['conditions']['Identifier.status'] = SuspendableStatusEnum::Active; - $args['contain'] = false; - - $Identifier = new Identifier(); - $grouper_identifier = $Identifier->find('first', $args); - - return $grouper_identifier['Identifier']['identifier']; - } - /** * Breakout Working Groups from AdHoc Groups for display purposes in UI. * * @param array $recordSet - * @param string $type 'basic' = view in Member page, 'admin' = view in Admin page, 'optin' = View in Optin page * @return array[] * */ - private function breakoutGroups(array $recordSet, $type = 'basic') + private function breakoutGroups(array $recordSet) { - //TODO - May move this logic down into the GrouperGroup model file, once all is agreed upon. $wgData = array(); $notWGData = array(); //Parse out the Working Groups from the Ad-hoc groups @@ -869,5 +661,69 @@ private function breakoutGroups(array $recordSet, $type = 'basic') ); } + /** + * Override the default sanity check performed in AppController + * + * @since COmanage Registry v4.3.0 + * @return Boolean True if sanity check is successful + */ + public function verifyRequestedId() { + return true; + } + + + /** + * For Models that accept a CO ID, find the provided CO ID. + * - precondition: A coid must be provided in $this->request (params or data) + * + * @since COmanage Registry v4.3.0 + * @return Integer The CO ID if found, or -1 if not + */ + + public function parseCOID($data = null) { + if(!empty($this->request->params["named"]["co"])) { + return $this->request->params["named"]["co"]; + } + if(!empty($this->request->params["named"]["glid"])) { + $connectionInfo = $this->CoGrouperLiteWidget->findById($this->passedArgs['glid']); + $this->CoGrouperLiteWidget->CoDashboardWidget->CoDashboard->id = $connectionInfo["CoDashboardWidget"]["co_dashboard_id"]; + $co_id = $this->CoGrouperLiteWidget->CoDashboardWidget->CoDashboard->field('co_id'); + + if(!empty($co_id)) { + return $co_id; + } + } + + return -1; + } + + /** + * Prepare a REST result HTTP header. + * - precondition: HTTP headers must not yet have been sent + * - postcondition: CakeResponse configured with header + * + * @param integer $status HTTP result code + * @param array $body HTTP result comment + * @param string $contentType HTTP Response content Type + * @param $statusTxt $contentType Status text + * + * @throws JsonException + * @since COmanage Registry v4.4.4 + */ + + public function restResponse(int $status, + array $body=[], + string $contentType = 'json', + ?string $statusTxt = null) { + if(isset($statusTxt)) { + // We need to update the text associated with $status + $this->response->httpCodes(array($status => $statusTxt)); + } + + $this->response->type($contentType); + $this->response->statusCode($status); + $this->response->body(json_encode($body, JSON_THROW_ON_ERROR)); + $this->response->send(); + } } \ No newline at end of file diff --git a/Controller/GrouperLiteAppController.php b/Controller/GrouperLiteAppController.php deleted file mode 100644 index 15ac4ac..0000000 --- a/Controller/GrouperLiteAppController.php +++ /dev/null @@ -1,7 +0,0 @@ -http = new GrouperHTTPWrapper(); - if (!CakeSession::check('Plugin.Grouper.Api.id')) { - CakeLog::write('error', __METHOD__ . ': No Widget record in Session'); - throw new GrouperLiteException("No GrouperLite instance captured"); + if (empty($cfg['CoGrouperLiteWidget']['id'])) { + throw new GrouperLiteWidgetException('No GrouperLite instance captured'); } - $connUrl = CakeSession::read('Plugin.Grouper.Api.url'); - $connVer = CakeSession::read('Plugin.Grouper.Api.version'); + $connUrl = $cfg['CoGrouperLiteWidget']['conn_url']; + $connVer = $cfg['CoGrouperLiteWidget']['conn_ver']; $this->config['fullUrl'] = $connUrl . $this->_urlServlet . $connVer; - $this->http->setUser(CakeSession::read('Plugin.Grouper.Api.user')); - $this->http->setPassword(CakeSession::read('Plugin.Grouper.Api.pass')); + $this->http->setUser($cfg['CoGrouperLiteWidget']['conn_user']); + $this->http->setPassword($cfg['CoGrouperLiteWidget']['conn_pass']); } /** @@ -79,7 +78,7 @@ public function __construct() * * @param array $queryData Array of conditions for querying * @return array Membership records that User is a member of in Grouper - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException */ public function getGrouperMemberOfGroups(array $queryData) { @@ -91,6 +90,7 @@ public function getGrouperMemberOfGroups(array $queryData) try { $results = $this->http->sendRequest('GET', $connectionUrl); + CakeLog::write('debug', __METHOD__ . '::response: ' . var_export($results, true)); // Parse out relevant records to send front end if (isset($results['WsGetGroupsLiteResult']['wsGroups']) && $results['WsGetGroupsLiteResult']['wsGroups'] != NULL) { @@ -108,12 +108,12 @@ public function getGrouperMemberOfGroups(array $queryData) * For Optin groups, calls Grouper WS to join or leave a group * * @param array $queryData Array of conditions for querying + * * @return bool True if join or leave successful, False if not - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException|JsonException */ public function grouperGroupLeaveOrJoin(array $queryData) { - $groupName = $queryData['groupName']; $userId = $queryData['userId']; $groupLeaveOrJoin = $queryData['LeaveJoin']; @@ -129,7 +129,7 @@ public function grouperGroupLeaveOrJoin(array $queryData) $resultGroup = 'wsGroupAssigned'; } else { CakeLog::write('error', __METHOD__ . ": Option of $groupLeaveOrJoin is not supported"); - throw new GrouperLiteException("Received option of $groupLeaveOrJoin which is not supported"); + throw new GrouperLiteWidgetException("Received option of $groupLeaveOrJoin which is not supported"); } //Build request logic @@ -149,9 +149,10 @@ public function grouperGroupLeaveOrJoin(array $queryData) $connectionUrl = "{$this->config['fullUrl']}/groups/$groupName/members"; try { - $results = $this->http->sendRequest('PUT', $connectionUrl, json_encode($groupCommand)); + $results = $this->http->sendRequest('PUT', $connectionUrl, json_encode($groupCommand, JSON_THROW_ON_ERROR)); + CakeLog::write('debug', __METHOD__ . '::response: ' . var_export($results, true)); - if (isset($results[$resultResponse][$resultGroup]) && $results[$resultResponse][$resultGroup] != NULL) { + if (isset($results[$resultResponse][$resultGroup])) { $groupAssigned = $results[$resultResponse][$resultGroup]['name']; if ($groupAssigned == urldecode($groupName)) { return true; @@ -172,7 +173,7 @@ public function grouperGroupLeaveOrJoin(array $queryData) * * @param array $queryData Array of conditions for querying * @return array Optin groups from Grouper - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException * */ public function getOptionalGroups(array $queryData) @@ -180,8 +181,9 @@ public function getOptionalGroups(array $queryData) try { $results = $this->useMembershipUrl($queryData); + CakeLog::write('debug', __METHOD__ . '::response: ' . var_export($results, true)); - if (isset($results['WsGetMembershipsResults']['wsGroups']) && $results['WsGetMembershipsResults']['wsGroups'] != NULL) { + if (isset($results['WsGetMembershipsResults']['wsGroups'])) { return $results['WsGetMembershipsResults']['wsGroups']; } } catch (Exception $e) { @@ -196,29 +198,28 @@ public function getOptionalGroups(array $queryData) * * @param array $queryData Array of conditions for querying * @return array Array of groups from Grouper - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException * */ public function getOwnedGroups(array $queryData) { + $admins = array(); + $updaters = array(); try { $queryData['groupType'] = 'admin'; $resultsAdmin = $this->useMembershipUrl($queryData); + CakeLog::write('debug', __METHOD__ . '::response admins: ' . var_export($resultsAdmin, true)); $queryData['groupType'] = 'update'; $resultsUpdate = $this->useMembershipUrl($queryData); + CakeLog::write('debug', __METHOD__ . '::response updates: ' . var_export($resultsUpdate, true)); - if (isset($resultsAdmin['WsGetMembershipsResults']['wsGroups']) && $resultsAdmin['WsGetMembershipsResults']['wsGroups'] != NULL) { + if (isset($resultsAdmin['WsGetMembershipsResults']['wsGroups'])) { $admins = $resultsAdmin['WsGetMembershipsResults']['wsGroups']; - } else { - $admins = array(); } - - if (isset($resultsUpdate['WsGetMembershipsResults']['wsGroups']) && $resultsUpdate['WsGetMembershipsResults']['wsGroups'] != NULL) { + if (isset($resultsUpdate['WsGetMembershipsResults']['wsGroups'])) { $updaters = $resultsUpdate['WsGetMembershipsResults']['wsGroups']; - } else { - $updaters = array(); } return $this->removeDuplicates($admins, $updaters); @@ -264,40 +265,43 @@ public function removeDuplicates(array $arrOne, array $arrTwo) * Get members associated to a specific Grouper Group * * @param array $queryData Array of conditions for querying + * * @return array Listing of Members belonging to Grouper Group - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException|JsonException */ - public function getMembersInGroup(array $queryData) - { + public function getMembersInGroup(array $queryData) { + //Build request logic + $usersToShow = array( + 'WsRestGetMembersRequest' => array( + 'actAsSubjectLookup' => array( + 'subjectId' => $queryData['userId'] + ), + 'wsGroupLookups' => array( + array('groupName' => $queryData['groupName']) + ), + 'subjectAttributeNames' => array('name') + ) + ); + $connectionUrl = "{$this->config['fullUrl']}/groups"; - try { - //Build request logic - $usersToShow = array( - "WsRestGetMembersRequest" => array( - "actAsSubjectLookup" => array( - "subjectId" => $queryData['userId'] - ), - "wsGroupLookups" => array( - array("groupName" => $queryData['groupName']) - ), - "subjectAttributeNames" => array( - "name" - ) - ) - ); + try { $this->http->setHeader(array('Content-Type' => 'application/json', 'Accept' => 'application/json')); - $connectionUrl = "{$this->config['fullUrl']}/groups"; - $results = $this->http->sendRequest('POST', $connectionUrl, json_encode($usersToShow)); + $results = $this->http->sendRequest('POST', $connectionUrl, json_encode($usersToShow, JSON_THROW_ON_ERROR)); + + CakeLog::write('debug', __METHOD__ . '::response: ' . var_export($results, true)); // Parse out relevant records to send front end - if(isset($results['WsGetMembersResults']['results'][0]['resultMetadata']['resultCode']) && $results['WsGetMembersResults']['results'][0]['resultMetadata']['resultCode'] != NULL){ - if ($results['WsGetMembersResults']['results'][0]['resultMetadata']['resultCode'] == 'GROUP_NOT_FOUND'){ - return array(array("sourceId" => "NoAccess", "name" => "", "id" => "")); - } + if(isset($results['WsGetMembersResults']['results'][0]['resultMetadata']['resultCode']) + && $results['WsGetMembersResults']['results'][0]['resultMetadata']['resultCode'] == 'GROUP_NOT_FOUND') { + return array(array( + 'sourceId' => 'NoAccess', + 'name' => '', + 'id' => '' + )); } - if (isset($results['WsGetMembersResults']['results'][0]['wsSubjects']) && $results['WsGetMembersResults']['results'][0]['wsSubjects'] != NULL) { + if (isset($results['WsGetMembersResults']['results'][0]['wsSubjects'])) { return $results['WsGetMembersResults']['results'][0]['wsSubjects']; } } catch (Exception $e) { @@ -312,35 +316,37 @@ public function getMembersInGroup(array $queryData) * Add a member to a specific Grouper Group * * @param array $queryData Array of conditions for querying + * * @return string Requests success or not - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException|JsonException */ public function addMemberToGroup(array $queryData) { + $groupName = $queryData['groupName']; + $connectionUrl = "{$this->config['fullUrl']}/groups/$groupName/members"; - try { - $groupName = $queryData['groupName']; - - //Build request logic - $usersToAdd = array( - "WsRestAddMemberRequest" => array( - "subjectLookups" => array( - array("subjectId" => $queryData['addUserId']), - ), - "replaceAllExisting" => "F", - "actAsSubjectLookup" => array( - "subjectId" => $queryData['userId'] - ) + //Build request logic + $usersToAdd = array( + 'WsRestAddMemberRequest' => array( + 'subjectLookups' => array( + array('subjectId' => $queryData['addUserId']), + ), + 'replaceAllExisting' => 'F', + 'actAsSubjectLookup' => array( + 'subjectId' => $queryData['userId'] ) - ); + ) + ); - $this->http->setHeader(array('Content-Type' => 'application/json', 'Accept' => 'application/json')); - $connectionUrl = "{$this->config['fullUrl']}/groups/$groupName/members"; + $this->http->setHeader(array('Content-Type' => 'application/json', 'Accept' => 'application/json')); + + try { + $results = $this->http->sendRequest('PUT', $connectionUrl, json_encode($usersToAdd, JSON_THROW_ON_ERROR)); - $results = $this->http->sendRequest('PUT', $connectionUrl, json_encode($usersToAdd)); + CakeLog::write('debug', __METHOD__ . '::response: ' . var_export($results, true)); // Parse out relevant records to send front end - if (isset($results['WsAddMemberResults']['results'][0]['resultMetadata']) && $results['WsAddMemberResults']['results'][0]['resultMetadata'] != NULL) { + if (isset($results['WsAddMemberResults']['results'][0]['resultMetadata'])) { return $results['WsAddMemberResults']['results'][0]['resultMetadata']['resultCode']; } } catch (Exception $e) { @@ -348,40 +354,45 @@ public function addMemberToGroup(array $queryData) throw $e; } - return ""; + return ''; } /** * Remove a member from a specific Grouper Group * - * @param array $queryData Array of conditions for querying + * @param array $queryData Array of conditions for querying + * * @return string Requests success or not - * @throws GrouperLiteException + * + * @throws GrouperLiteWidgetException + * @throws JsonException */ public function removeMemberToGroup(array $queryData) { + $groupName = $queryData['groupName']; + $connectionUrl = "{$this->config['fullUrl']}/groups/$groupName/members"; - try { - $groupName = $queryData['groupName']; - - //Build request logic - $userToDelete = array( - "WsRestDeleteMemberRequest" => array( - "subjectLookups" => array( - array("subjectId" => $queryData['remUserId']), - ), - "actAsSubjectLookup" => array( - "subjectId" => $queryData['userId'] - ) + //Build request logic + $userToDelete = array( + 'WsRestDeleteMemberRequest' => array( + 'subjectLookups' => array( + array('subjectId' => $queryData['remUserId']), + ), + 'actAsSubjectLookup' => array( + 'subjectId' => $queryData['userId'] ) - ); + ) + ); - $this->http->setHeader(array('Content-Type' => 'application/json', 'Accept' => 'application/json')); - $connectionUrl = "{$this->config['fullUrl']}/groups/$groupName/members"; - $results = $this->http->sendRequest('POST', $connectionUrl, json_encode($userToDelete)); + $this->http->setHeader(array('Content-Type' => 'application/json', 'Accept' => 'application/json')); + + try { + $results = $this->http->sendRequest('POST', $connectionUrl, json_encode($userToDelete, JSON_THROW_ON_ERROR)); + + CakeLog::write('debug', __METHOD__ . '::response: ' . var_export($results, true)); // Parse out relevant records to send front end - if (isset($results['WsDeleteMemberResults']['results'][0]['resultMetadata']) && $results['WsDeleteMemberResults']['results'][0]['resultMetadata'] != NULL) { + if (isset($results['WsDeleteMemberResults']['results'][0]['resultMetadata'])) { return $results['WsDeleteMemberResults']['results'][0]['resultMetadata']['resultCode']; } } catch (Exception $e) { @@ -389,7 +400,7 @@ public function removeMemberToGroup(array $queryData) throw $e; } - return ""; + return ''; } /** @@ -397,7 +408,7 @@ public function removeMemberToGroup(array $queryData) * * @param array $queryData Array of conditions for querying * @return array Group records associated to calling method - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException * * @see getOwnedStems() * @see getOptinGroups() @@ -408,48 +419,39 @@ private function useMembershipUrl(array $queryData) { $groupType = $queryData['groupType']; $userId = $queryData['userId']; - + if ($groupType == 'optins' || $groupType == 'optouts') { - $subjectId = "GrouperAll"; + $subjectId = 'GrouperAll'; } elseif ($groupType == 'admin' || $groupType == 'update' || $groupType == 'stemAdmin') { $subjectId = $userId; } else { CakeLog::write('error', __METHOD__ . ": Option of $groupType is not supported"); - throw new GrouperLiteException("Option of $groupType is not supported"); + throw new GrouperLiteWidgetException("Option of $groupType is not supported"); } - if ($groupType == 'optins' || $groupType == 'optouts') { + //Build request logic + $groupsToShow = []; + $groupsToShow['WsRestGetMembershipsRequest']['fieldName'] = $groupType; + $groupsToShow['WsRestGetMembershipsRequest']['wsSubjectLookups'][0]['subjectId'] = $subjectId; + + if ($groupType == 'optins' + || $groupType == 'optouts') { //Build request logic, 2 subjectId's, second is for when user in "Secret" Optin/Optout Group - $groupsToShow = array( - "WsRestGetMembershipsRequest" => array( - "fieldName" => $groupType, - "wsSubjectLookups" => array( - array("subjectId" => $subjectId), - array("subjectId" => $userId) - ) - ) - ); - } else { - //Build request logic - $groupsToShow = array( - "WsRestGetMembershipsRequest" => array( - "fieldName" => $groupType, - "wsSubjectLookups" => array( - array("subjectId" => $subjectId) - ) - ) - ); + $groupsToShow['WsRestGetMembershipsRequest']['wsSubjectLookups'][1]['subjectId'] = $userId; } $this->http->setHeader(array('Content-Type' => 'application/json', 'Accept' => 'application/json')); $connectionUrl = "{$this->config['fullUrl']}/memberships"; try { - return $this->http->sendRequest('POST', $connectionUrl, json_encode($groupsToShow)); + $results = $this->http->sendRequest('POST', $connectionUrl, json_encode($groupsToShow)); + CakeLog::write('debug', __METHOD__ . '::response: ' . var_export($results, true)); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } + + return $results; } /** @@ -457,7 +459,7 @@ private function useMembershipUrl(array $queryData) * * @param array $queryData Array of conditions and data adding new Grouper Group * @return array status and error message, if applicable - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException * */ public function createGroupWithTemplate(array $queryData) @@ -469,8 +471,7 @@ public function createGroupWithTemplate(array $queryData) //need to take out SympaDomain if domain not being created. if ($data['gsh_input_isSympa'] == 'false') { - unset($data['gsh_input_sympaDomain']); - unset($data['gsh_input_isSympaModerated']); + unset($data['gsh_input_sympaDomain'], $data['gsh_input_isSympaModerated']); } //Build request logic @@ -480,17 +481,17 @@ public function createGroupWithTemplate(array $queryData) } $groupToSave = array( - "WsRestGshTemplateExecRequest" => array( - "gshTemplateActAsSubjectLookup" => array( - "subjectSourceId" => "ldap", - "subjectId" => $userId + 'WsRestGshTemplateExecRequest' => array( + 'gshTemplateActAsSubjectLookup' => array( + 'subjectSourceId' => 'ldap', + 'subjectId' => $userId ), - "ownerStemLookup" => array( - "stemName" => "ref:incommon-collab" + 'ownerStemLookup' => array( + 'stemName' => 'ref:incommon-collab' ), - "ownerType" => "stem", - "configId" => "createNewWorkingGroup", - "inputs" => $inputFields + 'ownerType' => 'stem', + 'configId' => 'createNewWorkingGroup', + 'inputs' => $inputFields ) ); @@ -501,47 +502,40 @@ public function createGroupWithTemplate(array $queryData) $message = ''; try { $results = $this->http->sendRequest('POST', $connectionUrl, json_encode($groupToSave)); - - if (isset($results['WsGshTemplateExecResult']['resultMetadata']['resultCode'])) { - if (stripos($results['WsGshTemplateExecResult']['resultMetadata']['resultCode'], "INVALID", 0) !== false) { - // Need to see what error message is - if (isset($results['WsGshTemplateExecResult']['gshValidationLines'])) { - //Just grab first one, since do not want to overload the user with errors, plus they won't understand message - $errorMessage = $results['WsGshTemplateExecResult']['gshValidationLines'][0]['validationText']; - $status = false; - if (stripos($errorMessage, 'already exist', 0) !== false) { - $message = 'There already is a WG named: ' . $data['gsh_input_workingGroupExtension']; - } else { - $message = 'An error occurred, please try again later.'; - } - } else { - $status = false; - $message = 'An error occurred, please try again later.'; - } - } elseif (stripos($results['WsGshTemplateExecResult']['resultMetadata']['resultCode'], "EXCEPTION", 0) !== false) { - throw new GrouperLiteException("An error occurred in creating your Working Group!"); - } - } } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } - return array( - 'status' => $status, - 'message' => $message - ); + if (isset($results['WsGshTemplateExecResult']['resultMetadata']['resultCode'])) { + $status = false; + $message = 'An error occurred, please try again later.'; + + CakeLog::write('error', __METHOD__ . ': An error occurred in creating your Working Group!'); + + if (isset($results['WsGshTemplateExecResult']['gshValidationLines'][0]['validationText']) + && stripos( + $results['WsGshTemplateExecResult']['gshValidationLines'][0]['validationText'], + 'already exist', + 0 ) !== false) + { + $message = 'There already is a WG named: ' . $data['gsh_input_workingGroupExtension']; + } elseif (stripos($results['WsGshTemplateExecResult']['resultMetadata']['resultCode'], 'EXCEPTION', 0) !== false) { + throw new GrouperLiteWidgetException('An error occurred in creating your Working Group!'); + } + } + return compact('status', 'message'); } /** - * ======================== NOT BEING USED ======================== * * Method used to DELETE a Group in Grouper via the Template method. * * @param array $queryData Array of conditions and data adding new Grouper Group + * * @return bool True if deleted successfully - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException|JsonException * */ public function deleteGroupWithTemplate(array $queryData) @@ -551,20 +545,20 @@ public function deleteGroupWithTemplate(array $queryData) $userId = $queryData['userId']; $groupToDelete = array( - "WsRestGshTemplateExecRequest" => array( - "gshTemplateActAsSubjectLookup" => array( - "subjectSourceId" => "ldap", - "subjectId" => $userId + 'WsRestGshTemplateExecRequest' => array( + 'gshTemplateActAsSubjectLookup' => array( + 'subjectSourceId' => 'ldap', + 'subjectId' => $userId ), - "ownerStemLookup" => array( - "stemName" => "ref:incommon-collab" + 'ownerStemLookup' => array( + 'stemName' => 'ref:incommon-collab' ), - "ownerType" => "stem", - "configId" => "createWorkingGroup", - "inputs" => array( + 'ownerType' => 'stem', + 'configId' => 'createWorkingGroup', + 'inputs' => array( array( - "name" => "gsh_input_workingGroupExtension", - "value" => $workingGroupExt + 'name' => 'gsh_input_workingGroupExtension', + 'value' => $workingGroupExt ) ) ) @@ -574,30 +568,34 @@ public function deleteGroupWithTemplate(array $queryData) $connectionUrl = "{$this->config['fullUrl']}/gshTemplateExec"; try { - $results = $this->http->sendRequest('POST', $connectionUrl, json_encode($groupToDelete)); - - if (isset($results['WsGshTemplateExecResult']['resultMetadata']['resultCode'])) { - if (stripos($results['WsGshTemplateExecResult']['resultMetadata']['resultCode'], "SUCCESS", 0) !== false) { - return true; - } - } + $results = $this->http->sendRequest('POST', $connectionUrl, json_encode($groupToDelete, JSON_THROW_ON_ERROR)); + CakeLog::write('debug', __METHOD__ . '::response: ' . var_export($results, true)); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } + + if (isset($results['WsGshTemplateExecResult']['resultMetadata']['resultCode']) && stripos( + $results['WsGshTemplateExecResult']['resultMetadata']['resultCode'], + 'SUCCESS', + 0 + ) !== false) { + return true; + } + return false; } /** - * ======================== NOT BEING USED ======================== * * For creating/updating Ad-Hoc groups not using the Grouper Template format * * Create or Update a Group where User is Admin/Owner * * @param array $queryData Array of conditions and data adding new Grouper Group + * * @return bool True if added or updated successful - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException|JsonException */ public function createUpdateGroup(array $queryData) { @@ -606,7 +604,6 @@ public function createUpdateGroup(array $queryData) $stemName = htmlentities($queryData['stem']); $userId = $queryData['userId']; $groupDescription = htmlentities($queryData['description']); - $privileges = $queryData['privileges']; //Group name may be in "friendly" format, so need to do some small conversions before saving. $newGroupName = ucfirst(strtolower($groupName)); @@ -616,19 +613,19 @@ public function createUpdateGroup(array $queryData) //Build request logic $groupToSave = array( - "WsRestGroupSaveRequest" => array( - "actAsSubjectLookup" => array( - "subjectId" => $userId + 'WsRestGroupSaveRequest' => array( + 'actAsSubjectLookup' => array( + 'subjectId' => $userId ), - "wsGroupToSaves" => array( + 'wsGroupToSaves' => array( array( - "wsGroup" => array( - "description" => $groupDescription, - "name" => $newGroupToSave, - "displayExtension" => $groupName + 'wsGroup' => array( + 'description' => $groupDescription, + 'name' => $newGroupToSave, + 'displayExtension' => $groupName ), - "wsGroupLookup" => array( - "groupName" => $groupDescription + 'wsGroupLookup' => array( + 'groupName' => $groupDescription ) ) ) @@ -638,29 +635,34 @@ public function createUpdateGroup(array $queryData) $connectionUrl = "{$this->config['fullUrl']}/groups"; try { - $results = $this->http->sendRequest('POST', $connectionUrl, json_encode($groupToSave)); - - if (isset($results['WsGroupSaveResults']['results']['resultMetadata']['resultCode'])) { - if (stripos($results['WsGroupSaveResults']['results']['resultMetadata']['resultCode'], "Success", 0) !== false) { - return true; - } - } + $results = $this->http->sendRequest('POST', $connectionUrl, json_encode($groupToSave, JSON_THROW_ON_ERROR)); + CakeLog::write('debug', __METHOD__ . '::response: ' . var_export($results, true)); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } + + if (isset($results['WsGroupSaveResults']['results']['resultMetadata']['resultCode']) + && stripos( + $results['WsGroupSaveResults']['results']['resultMetadata']['resultCode'], + 'Success', + 0 + ) !== false) { + return true; + } + return false; } /** - * ======================== NOT BEING USED ======================== * Used to be used for Grouper group info page. * * Grouper Group information plus a listing of attributes in Grouper for that given Group * * @param array $queryData Array of conditions for querying + * * @return array Record of Grouper attributes for given GroupName - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException|JsonException */ public function getGrouperGroupInfo(array $queryData) { @@ -669,12 +671,12 @@ public function getGrouperGroupInfo(array $queryData) //Build request logic $stemToFind = array( - "WsRestGetAttributeAssignmentsRequest" => array( - "attributeAssignType" => "group", - "includeAssignmentsOnAssignments" => "T", - "wsOwnerGroupLookups" => array( + 'WsRestGetAttributeAssignmentsRequest' => array( + 'attributeAssignType' => 'group', + 'includeAssignmentsOnAssignments' => 'T', + 'wsOwnerGroupLookups' => array( array( - "groupName" => $groupName, + 'groupName' => $groupName, ) ) ) @@ -683,25 +685,23 @@ public function getGrouperGroupInfo(array $queryData) $connectionUrl = "{$this->config['fullUrl']}/attributeAssignments"; try { - $results = $this->http->sendRequest('POST', $connectionUrl, json_encode($stemToFind)); + $results = $this->http->sendRequest('POST', $connectionUrl, json_encode($stemToFind, JSON_THROW_ON_ERROR)); + CakeLog::write('debug', __METHOD__ . '::response: ' . var_export($results, true)); - //Get the group information - if (isset($results['WsGetAttributeAssignmentsResults']['wsGroups']) && $results['WsGetAttributeAssignmentsResults']['wsGroups'] != NULL) { - $groupInfo = $results['WsGetAttributeAssignmentsResults']['wsGroups']; - } - - //Now get the Group Attributes and add them to group - if (isset($results['WsGetAttributeAssignmentsResults']['wsAttributeAssigns']) && $results['WsGetAttributeAssignmentsResults']['wsAttributeAssigns'] != NULL) { - $groupInfo[0]["attributes"] = $results['WsGetAttributeAssignmentsResults']['wsAttributeAssigns']; - } else { - $groupInfo[0]["attributes"] = array(); - } - - return $groupInfo; } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } + + //Get the group information + if (isset($results['WsGetAttributeAssignmentsResults']['wsGroups'])) { + $groupInfo = $results['WsGetAttributeAssignmentsResults']['wsGroups']; + } + + //Now get the Group Attributes and add them to group + $groupInfo[0]['attributes'] = $results['WsGetAttributeAssignmentsResults']['wsAttributeAssigns'] ?? array(); + + return $groupInfo; } /** @@ -713,7 +713,7 @@ public function getGrouperGroupInfo(array $queryData) * * @param array $queryData Array of conditions for querying * @return array Array of Stems/Folders from Grouper - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException */ public function getOwnedStems(array $queryData) { diff --git a/Lib/GrouperHTTPWrapper.php b/Lib/GrouperHTTPWrapper.php index dbcd822..9df9640 100644 --- a/Lib/GrouperHTTPWrapper.php +++ b/Lib/GrouperHTTPWrapper.php @@ -26,7 +26,7 @@ */ App::uses('HttpSocket', 'Network/Http'); -App::uses('GrouperLiteException', 'GrouperLite.Lib/'); +App::uses('GrouperLiteWidgetException', 'GrouperLiteWidget.Lib/'); /** * Class GrouperHTTPWrapper @@ -102,7 +102,7 @@ public function setHeader(array $headerSetting) { * @param string $uri Grouper RESTful endpoint with request string, if applicable * @param string $body JSON formatted request, if applicable * @return array array of records | array with error message - * @throws GrouperLiteException If issue with Grouper WS connection + * @throws GrouperLiteWidgetException If issue with Grouper WS connection */ public function sendRequest(string $method, string $uri, string $body = ''): array { @@ -116,7 +116,7 @@ public function sendRequest(string $method, string $uri, string $body = ''): arr $apiResults = $this->request($this->_request); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred: ' . var_export($e->getMessage(), true)); - throw new GrouperLiteException('An error occurred talking to Grouper WS'); + throw new GrouperLiteWidgetException('An error occurred talking to Grouper WS'); } // Call may return non-200, which may be okay depending on call diff --git a/Lib/GrouperLiteException.php b/Lib/GrouperLiteWidgetException.php similarity index 94% rename from Lib/GrouperLiteException.php rename to Lib/GrouperLiteWidgetException.php index 6e4621b..7ac0890 100644 --- a/Lib/GrouperLiteException.php +++ b/Lib/GrouperLiteWidgetException.php @@ -25,10 +25,10 @@ * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) */ -class GrouperLiteException extends Exception +class GrouperLiteWidgetException extends Exception { /** - * GrouperLiteException constructor. + * GrouperLiteWidgetException constructor. * @param $message * @param int $code * @param Exception|null $previous diff --git a/Lib/empty b/Lib/empty deleted file mode 100644 index e69de29..0000000 diff --git a/Lib/enum.php b/Lib/enum.php new file mode 100644 index 0000000..dd4aaa0 --- /dev/null +++ b/Lib/enum.php @@ -0,0 +1,9 @@ + 'ERROR', 'message' => 'EXCEPTION'); + const NotDeleted = array('status' => 'ERROR', 'message' => 'NOT DELETED'); + const NotAdded = array('status' => 'ERROR', 'message' => 'NOT ADDED'); + const Error = array('status' => 'ERROR', 'message' => 'ERROR'); + const NoAccess = array('status' => 'ERROR', 'message' => 'NO ACCESS'); +} \ No newline at end of file diff --git a/Lib/lang.php b/Lib/lang.php index bcae1dd..8c26b24 100644 --- a/Lib/lang.php +++ b/Lib/lang.php @@ -1,6 +1,6 @@ 'Grouper Configuration Settings', 'pl.grouperlite.config.edit.title' => 'Edit Grouper Configuration Settings', 'pl.grouperlite.config.grouper-url' => 'Grouper Site URL', @@ -15,6 +15,8 @@ 'pl.grouperlite.config.wg-subscript' => '', 'pl.grouperlite.config.default-collapse' => 'Collapse groups by default?', 'pl.grouperlite.config.default-collapse-subscript' => '', + 'pl.grouperlite.config.identifier' => 'Identifier Type', + 'pl.grouperlite.config.identifier.desc' => 'Identifier eligible to make requests to Grouper', 'pl.grouperlite.crumb.root' => 'Grouper', 'pl.grouperlite.nav.groups-can-join' => 'Groups I can join', @@ -166,5 +168,7 @@ 'pl.grouperlite.working-groups.zero-state' => 'None.', 'pl.grouperlite.email-lists.zero-state' => 'No email lists found.', 'pl.grouperlite.members.noaccess' => 'You do not have access to view memberships.', - 'pl.grouperlite.members.empty' => 'This group has no member OR you are not authorized to see the members of this group.' + 'pl.grouperlite.members.empty' => 'This group has no member OR you are not authorized to see the members of this group.', + + 'er.grouperlite.glid' => 'Named parameter glid was not found', ); \ No newline at end of file diff --git a/Model/CoGrouperLite.php b/Model/CoGrouperLite.php deleted file mode 100644 index 1226065..0000000 --- a/Model/CoGrouperLite.php +++ /dev/null @@ -1,98 +0,0 @@ - array( - "parent" => "CoDashboardWidget", - "fk" => "co_dashboard_widget_id" - ) - );*/ - - // Validation rules for table elements - //Tried adding rule to each field to make alphaNumeric, but keeps throwing errors. will research. - public $validate = array( - 'co_dashboard_widget_id' => array( - 'rule' => 'alphaNumeric', - 'required' => true - ), - 'conn_url' => array( - 'rule' => array('custom', '/^https?:\/\/.*/'), - 'required' => true - ), - 'conn_ver' => array( - 'rule' => array('minLength', 4), - 'required' => true - ), - 'grouper_url' => array( - 'rule' => array('custom', '/^https?:\/\/.*/'), - 'required' => true - ), - 'conn_user' => array( - 'rule' => array('minLength', 1), - 'required' => true - ), - 'conn_pass' => array( - 'rule' => array('minLength', 1), - 'required' => true - ), - 'adhoc_heading' => array( - 'rule' => array('minLength', 1), - 'required' => false, - 'default' => 'Ad-hoc groups' - ), - 'wg_heading' => array( - 'rule' => array('minLength', 1), - 'required' => false, - 'default' => 'Working groups' - ), - 'default_collapse' => array( - 'rule' => array('minLength', 1), - 'required' => false, - 'default' => 'collapsed' - ), - ); - -} \ No newline at end of file diff --git a/Model/CoGrouperLiteWidget.php b/Model/CoGrouperLiteWidget.php new file mode 100644 index 0000000..2f57fd2 --- /dev/null +++ b/Model/CoGrouperLiteWidget.php @@ -0,0 +1,152 @@ + array( + 'rule' => 'numeric', + 'required' => true, + 'allowEmpty' => false + ), + 'conn_url' => array( + 'rule' => array('custom', '/^https?:\/\/.*/'), + 'required' => true, + 'allowEmpty' => false + ), + 'conn_ver' => array( + 'rule' => array('minLength', 4), + 'required' => true, + 'allowEmpty' => false + ), + 'grouper_url' => array( + 'rule' => array('custom', '/^https?:\/\/.*/'), + 'required' => true, + 'allowEmpty' => false + ), + 'conn_user' => array( + 'rule' => array('minLength', 1), + 'required' => true, + 'allowEmpty' => false + ), + 'conn_pass' => array( + 'rule' => array('minLength', 1), + 'required' => true, + 'allowEmpty' => false + ), + 'adhoc_heading' => array( + 'rule' => array('minLength', 1), + 'required' => false, + 'allowEmpty' => true, + 'default' => 'Ad-hoc groups' + ), + 'wg_heading' => array( + 'rule' => array('minLength', 1), + 'required' => false, + 'allowEmpty' => true, + 'default' => 'Working groups' + ), + 'default_collapse' => array( + 'rule' => array('minLength', 1), + 'required' => false, + 'default' => 'collapsed' + ), + 'identifier_type' => array( + 'content' => array( + 'rule' => array( + 'validateExtendedType', + array('attribute' => 'Identifier.type', + 'default' => array(IdentifierEnum::AffiliateSOR, + IdentifierEnum::Badge, + IdentifierEnum::Enterprise, + IdentifierEnum::ePPN, + IdentifierEnum::ePTID, + IdentifierEnum::ePUID, + IdentifierEnum::GuestSOR, + IdentifierEnum::HRSOR, + IdentifierEnum::Mail, + IdentifierEnum::National, + IdentifierEnum::Network, + IdentifierEnum::OIDCsub, + IdentifierEnum::OpenID, + IdentifierEnum::ORCID, + IdentifierEnum::ProvisioningTarget, + IdentifierEnum::Reference, + IdentifierEnum::SamlPairwise, + IdentifierEnum::SamlSubject, + IdentifierEnum::SORID, + IdentifierEnum::StudentSOR, + IdentifierEnum::UID))), + 'required' => true, + 'allowEmpty' => false + ) + ), + ); + + /** + * Actions to take before a validate operation is executed. + * + * @since COmanage Registry v4.4.0 + */ + + public function beforeValidate($options = array()) { + if(!empty($this->data[$this->alias]['co_dashboard_widget_id']) + && isset($this->id)) { + // Dashboard Widget Plugins will refer to Dashboard Widget, which in turn + // refers to a Dashboard + + $args = array(); + $args['conditions'][$this->alias.'.id'] = $this->id; + $args['contain']['CoDashboardWidget'][] = 'CoDashboard'; + + $codw = $this->find('first', $args); + + if(!empty($codw["CoDashboardWidget"]["CoDashboard"]["co_id"])) { + $contentRule = $this->validator()->getField('identifier_type')->getRule('content')->rule; + $contentRule[1]['coid'] = $codw["CoDashboardWidget"]["CoDashboard"]["co_id"]; + $this->validator()->getField('identifier_type')->getRule('content')->rule = $contentRule; + } + } + + return parent::beforeValidate($options); + } + +} \ No newline at end of file diff --git a/Model/CoManagePerson.php b/Model/CoManagePerson.php index 8d999c9..c8f1cc3 100644 --- a/Model/CoManagePerson.php +++ b/Model/CoManagePerson.php @@ -2,7 +2,7 @@ App::uses('CoPersonRole', 'Model/'); -class CoManagePerson extends GrouperLiteAppModel +class CoManagePerson extends GrouperLiteWidgetAppModel { public $name = "CoManagePerson"; diff --git a/Model/GrouperAttribute.php b/Model/GrouperAttribute.php index 7e00fe1..4df4c4d 100644 --- a/Model/GrouperAttribute.php +++ b/Model/GrouperAttribute.php @@ -25,14 +25,14 @@ * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) */ -App::uses('GrouperApiAccess', 'GrouperLite.Lib/'); +App::uses('GrouperApiAccess', 'GrouperLiteWidget.Lib/'); /** * Class GrouperAttribute * * Model class to get attributes from Grouper */ -class GrouperAttribute extends GrouperLiteAppModel +class GrouperAttribute extends GrouperLiteWidgetAppModel { /** @var string $name used by CakePHP for locating model */ @@ -44,9 +44,9 @@ class GrouperAttribute extends GrouperLiteAppModel /** * Used to instantiate API class */ - private function initApi() { + private function initApi(array $cfg) { if ($this->grouperAPI == null) { - $this->grouperAPI = new GrouperApiAccess(); + $this->grouperAPI = new GrouperApiAccess($cfg); } } @@ -55,10 +55,10 @@ private function initApi() { * * @param string $groupName Name of Group * @return array Attributes in Grouper for this Group - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException */ public function getGroupAttributes(string $groupName) { - $this->initApi(); + $this->initApi($cfg); try { $args = array(); diff --git a/Model/GrouperGroup.php b/Model/GrouperGroup.php index 9bff932..3baa9fd 100644 --- a/Model/GrouperGroup.php +++ b/Model/GrouperGroup.php @@ -25,24 +25,22 @@ * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) */ -App::uses('GrouperApiAccess', 'GrouperLite.Lib/'); -App::uses('GrouperAttribute', 'GrouperLite.Model/'); +App::uses('GrouperApiAccess', 'GrouperLiteWidget.Lib/'); +App::uses('GrouperAttribute', 'GrouperLiteWidget.Model/'); /*** * Class GrouperGroup * * Model class that does most of the heavy lifting in Grouper Lite Widget */ -class GrouperGroup extends GrouperLiteAppModel +class GrouperGroup extends GrouperLiteWidgetAppModel { /** @var string $name used by CakePHP for locating model */ public $name = "GrouperGroup"; /** @var GrouperApiAccess $grouperAPI */ - public $grouperAPI = null; - - private $totalRecords = 0; + private $grouperAPI = null; /** @var string Group whose members can create Groups via Template process */ private $templateCreationGroup = 'ref:workinggroupadmins'; @@ -75,18 +73,14 @@ class GrouperGroup extends GrouperLiteAppModel * * @param string $userId Id of User * @return String T or F - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException * * @see GrouperGroup::resetUserOwner() * */ - public function isUserOwner(string $userId) + public function isUserOwner(string $userId, array $cfg) { - if (CakeSession::check('Plugin.Grouper.isUserOwner')) { - return CakeSession::read('Plugin.Grouper.isUserOwner'); - } - - $this->initApi(); + $this->initApi($cfg); try { $args = array(); @@ -94,13 +88,7 @@ public function isUserOwner(string $userId) $ownGroups = $this->grouperAPI->getOwnedGroups($args); - if (count($ownGroups) > 0) { - CakeSession::write('Plugin.Grouper.isUserOwner', 'T'); - return 'T'; - } - CakeSession::write('Plugin.Grouper.isUserOwner', 'F'); - return 'F'; - + return count($ownGroups) > 0 ? 'T' : 'F'; } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; @@ -112,16 +100,12 @@ public function isUserOwner(string $userId) * * @param string $userId Id of User * @return String T or F - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException * */ - public function isGrouperVisible(string $userId) + public function isGrouperVisible(string $userId, array $cfg) { - if (CakeSession::check('Plugin.Grouper.isGrouperVisible')) { - return CakeSession::read('Plugin.Grouper.isGrouperVisible'); - } - - $this->initApi(); + $this->initApi($cfg); try { $args = array(); @@ -129,8 +113,6 @@ public function isGrouperVisible(string $userId) $memberOfGroups = $this->grouperAPI->getGrouperMemberOfGroups($args); - $memberOfGroups = $this->grouperAPI->getGrouperMemberOfGroups($args); - //now cycle through and see if part of correct group to be able to use template $member = 'F'; foreach ($memberOfGroups as $memberOfGroup) { @@ -140,14 +122,7 @@ public function isGrouperVisible(string $userId) } } - if ($member == 'T') { - CakeSession::write('Plugin.Grouper.isGrouperVisible', 'T'); - return 'T'; - } else { - CakeSession::write('Plugin.Grouper.isGrouperVisible', 'F'); - return 'F'; - } - + return $member; } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; @@ -162,10 +137,10 @@ public function isGrouperVisible(string $userId) * so error was being thrown. * Now will call this before each function call to verify set. */ - private function initApi() + private function initApi(array $cfg) { if ($this->grouperAPI == null) { - $this->grouperAPI = new GrouperApiAccess(); + $this->grouperAPI = new GrouperApiAccess($cfg); } } @@ -175,12 +150,12 @@ private function initApi() * * @param array $conditions Listing of conditions for display of records, including UserId * @return array Records of Groups from Grouper that the User belongs to - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException * */ - public function filteredMemberOfGroups(array $conditions) + public function filteredMemberOfGroups(array $conditions, array $cfg) { - $this->initApi(); + $this->initApi($cfg); try { @@ -217,7 +192,7 @@ public function filteredMemberOfGroups(array $conditions) * * @param array $conditions Listing of conditions for display of records, including UserId * @return array Records of Groups from Grouper that the User belongs to - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException * */ private function memberOfGroups(array $conditions) @@ -238,13 +213,15 @@ private function memberOfGroups(array $conditions) * * @param string $userId Id of User * @param string $groupName Name of Group Leaving, do not confuse with DisplayName field! + * @param array $cfg the plugin configuration + * * @return bool True|False - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException * */ - public function leaveGroup(string $userId, string $groupName) + public function leaveGroup(string $userId, string $groupName, array $cfg) { - $this->initApi(); + $this->initApi($cfg); try { $args = array(); @@ -266,12 +243,12 @@ public function leaveGroup(string $userId, string $groupName) * @param string $userId Id of User * @param string $groupName Name of Group Joining, do not confuse with DisplayName field! * @return bool True|False - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException * */ - public function joinGroup(string $userId, string $groupName) + public function joinGroup(string $userId, string $groupName, array $cfg) { - $this->initApi(); + $this->initApi($cfg); try { $args = array(); @@ -292,12 +269,12 @@ public function joinGroup(string $userId, string $groupName) * * @param array $conditions Listing of conditions for display of records, including UserId * @return array - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException * */ - public function ownerGroups(array $conditions) + public function ownerGroups(array $conditions, array $cfg) { - $this->initApi(); + $this->initApi($cfg); try { $resultSet = $this->grouperAPI->getOwnedGroups($conditions); @@ -317,12 +294,12 @@ public function ownerGroups(array $conditions) * @param array $conditions Listing of conditions for display of records * @param string $userId Id of User * @return array Listing of members in requested Grouper Group - * @throws GrouperLiteException Captured in Controller + * @throws GrouperLiteWidgetException Captured in Controller * */ - public function membersInGroup(array $conditions, string $userId) + public function membersInGroup(array $conditions, string $userId, array $cfg) { - $this->initApi(); + $this->initApi($cfg); $conditions['userId'] = $userId; @@ -354,12 +331,12 @@ public function membersInGroup(array $conditions, string $userId) * @param array $conditions Listing of conditions for display of records * @param string $userId Id of User * @return string success of Request - * @throws GrouperLiteException Captured in Controller + * @throws GrouperLiteWidgetException Captured in Controller * */ - public function addMemberToGroup(array $conditions, string $userId) + public function addMemberToGroup(array $conditions, string $userId, array $cfg) { - $this->initApi(); + $this->initApi($cfg); $conditions['userId'] = $userId; @@ -380,20 +357,17 @@ public function addMemberToGroup(array $conditions, string $userId) * @param array $conditions Listing of conditions for display of records * @param string $userId Id of User * @return string success of Request - * @throws GrouperLiteException Captured in Controller + * @throws GrouperLiteWidgetException Captured in Controller * */ - public function removeMemberToGroup(array $conditions, string $userId) + public function removeMemberToGroup(array $conditions, string $userId, array $cfg) { - $this->initApi(); + $this->initApi($cfg); $conditions['userId'] = $userId; try { - $resultRemove = $this->grouperAPI->removeMemberToGroup($conditions); - - return $resultRemove; - + return $this->grouperAPI->removeMemberToGroup($conditions); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; @@ -408,11 +382,11 @@ public function removeMemberToGroup(array $conditions, string $userId) * * @param array $conditions Listing of conditions for display of records, including UserId * @return array Listing of Optin groups available in Grouper - * @throws GrouperLiteException Captured in Controller + * @throws GrouperLiteWidgetException Captured in Controller */ - public function optinGroups(array $conditions) + public function optinGroups(array $conditions, array $cfg) { - $this->initApi(); + $this->initApi($cfg); try { $conditions['groupType'] = 'optins'; @@ -445,15 +419,11 @@ public function optinGroups(array $conditions) * * @param string $userId Id of User * @return string T for True and F for False - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException */ - public function isTemplateUser(string $userId) + public function isTemplateUser(string $userId, array $cfg) { - if (CakeSession::check('Plugin.Grouper.isTemplateUser')) { - return CakeSession::read('Plugin.Grouper.isTemplateUser'); - } - - $this->initApi(); + $this->initApi($cfg); try { $args = array(); @@ -470,13 +440,7 @@ public function isTemplateUser(string $userId) } } - if ($member == 'T') { - CakeSession::write('Plugin.Grouper.isTemplateUser', 'T'); - return 'T'; - } - CakeSession::write('Plugin.Grouper.isTemplateUser', 'F'); - return 'F'; - + return $member; } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; @@ -490,12 +454,12 @@ public function isTemplateUser(string $userId) * @param string $userId Id of User * @param array $groupData Data needed to create new Grouper Group via Template * @return array status and error message, if applicable - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException * */ - public function createGroupWithTemplate(string $userId, array $groupData) + public function createGroupWithTemplate(string $userId, array $groupData, array $cfg) { - $this->initApi(); + $this->initApi($cfg); try { //Need to massage incoming data to meet Grouper Template requirements @@ -527,19 +491,6 @@ public function createGroupWithTemplate(string $userId, array $groupData) } - /** - * Private function used to reset the IsUserOwner Session variable, - * that maintains the status of a user being an owner/admin of a group - * - * @see GrouperGroup::isUserOwner() - */ - private function resetUserOwner() - { - if (CakeSession::check('Plugin.Grouper.isUserOwner')) { - CakeSession::delete('Plugin.Grouper.isUserOwner'); - } - } - /** * Search for Groups/Lists related to Search term. * @@ -551,9 +502,9 @@ private function resetUserOwner() * @return array Records that meet search criteria * @throws Exception Captured in Controller */ - public function getSearchedGroups(array $conditions) + public function getSearchedGroups(array $conditions, array $cfg) { - $this->initApi(); + $this->initApi($cfg); try { //Breakout page where search was called and forward to appropriate method for processing @@ -605,6 +556,9 @@ private function getFriendlyWorkingGroupName(array $groups, $method) $arrayIndex = 0; $workingGroups = array(); + + CakeLog::write('debug', __METHOD__ . '::raw groups: ', print_r($groups, true)); + //First need to loop through all groups and pull in all top levels $topLevelWG = array(); foreach ($groups as $group) { diff --git a/Model/GrouperLite.php b/Model/GrouperLiteWidget.php similarity index 79% rename from Model/GrouperLite.php rename to Model/GrouperLiteWidget.php index dec2df3..bfa3d4a 100644 --- a/Model/GrouperLite.php +++ b/Model/GrouperLiteWidget.php @@ -25,33 +25,26 @@ * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) */ -class GrouperLite extends AppModel { +class GrouperLiteWidget extends AppModel { // Define class name for cake - public $name = "GrouperLite"; + public $name = "GrouperLiteWidget"; // Required by COmanage Plugins public $cmPluginType = "dashboardwidget"; + // Document foreign keys + public $cmPluginHasMany = array(); + // Association rules from this model to other models - public $belongsTo = array( - ); + public $belongsTo = array(); - public $hasMany = array( - ); + public $hasMany = array(); // Validation rules for table elements - public $validate = array( - ); + public $validate = array(); public function cmPluginMenus() { - return array( - /*"cogroups" => array( - 'Grouper groups' => array( - 'controller' => "groupergroups", - 'action' => "groupoptin" - ) - )*/ - ); + return array(); } diff --git a/Model/GrouperLiteAppModel.php b/Model/GrouperLiteWidgetAppModel.php similarity index 95% rename from Model/GrouperLiteAppModel.php rename to Model/GrouperLiteWidgetAppModel.php index 18cbb6d..be6cb29 100644 --- a/Model/GrouperLiteAppModel.php +++ b/Model/GrouperLiteWidgetAppModel.php @@ -27,6 +27,6 @@ App::uses('AppModel', 'Model'); -class GrouperLiteAppModel extends AppModel { +class GrouperLiteWidgetAppModel extends AppModel { } diff --git a/View/CoGrouperLites/display.ctp b/View/CoGrouperLiteWidgets/display.ctp similarity index 87% rename from View/CoGrouperLites/display.ctp rename to View/CoGrouperLiteWidgets/display.ctp index 480ee25..ccf0c76 100644 --- a/View/CoGrouperLites/display.ctp +++ b/View/CoGrouperLiteWidgets/display.ctp @@ -31,35 +31,36 @@ // Figure out the widget ID so we can overwrite the dashboard's widget div -$divid = $vv_config['CoGrouperLite']['co_dashboard_widget_id']; +$divid = $vv_config['CoGrouperLiteWidget']['co_dashboard_widget_id']; -// $coid = $config['co']; -// $glid = $config['glid']; +$plugin = filter_var($vv_codw["CoDashboardWidget"]["plugin"],FILTER_SANITIZE_SPECIAL_CHARS); +$pl = Inflector::underscore($plugin); +$plcmodel = Inflector::pluralize($pl); $this->extend('/GrouperGroups/base'); -echo $this->element('GrouperLite.base-styles'); +echo $this->element('GrouperLiteWidget.base-styles'); $idsuffix = rand(); ?> - -Form->end(); ?> \ No newline at end of file diff --git a/View/Elements/Components/subscriberList.ctp b/View/Elements/Components/subscriberList.ctp deleted file mode 100644 index 451f07d..0000000 --- a/View/Elements/Components/subscriberList.ctp +++ /dev/null @@ -1,300 +0,0 @@ -
-
- -
- - - - - - - \ No newline at end of file diff --git a/View/Elements/Components/vue-table.ctp b/View/Elements/Components/vue-table.ctp deleted file mode 100644 index d06c04c..0000000 --- a/View/Elements/Components/vue-table.ctp +++ /dev/null @@ -1,137 +0,0 @@ - - - - -
- element('pagination', array( - 'goto' => false, - 'limit' => false, - 'numbers' => false, - 'counter' => true, - 'class' => 'counter' - )); ?> -
- - - -
- - element("pagination", array( - 'goto' => false, - 'limit' => true, - 'numbers' => true, - 'counter' => false - )); ?> -
diff --git a/View/Elements/pagination.ctp b/View/Elements/pagination.ctp deleted file mode 100644 index a182dbd..0000000 --- a/View/Elements/pagination.ctp +++ /dev/null @@ -1,141 +0,0 @@ - - - - -
- -
- Paginator->counter(array( - 'format' => _txt(is_string($counter) ? $counter : 'pl.grouperlite.pagination.counter') - )); - ?> -
- - - Paginator->hasPage(2)) : ?> -
- - -
- Paginator->numbers(array( - 'separator' => '', - 'class' => 'pagination-numbers-item' - )); - ?> -
- - -
- - - Paginator->hasPage(2)) : ?> - -
- - - -
- - - - - -
- - -

- - -
- -
\ No newline at end of file diff --git a/View/GrouperGroups/groupinfo.ctp b/View/GrouperGroups/groupinfo.ctp deleted file mode 100644 index 7aed583..0000000 --- a/View/GrouperGroups/groupinfo.ctp +++ /dev/null @@ -1,46 +0,0 @@ - -extend('/GrouperGroups/base'); ?> -Html->addCrumb(_txt('pl.grouperlite.title.groupinfo')); ?> - - -
-
-

-
- -
-
-
-
-
-

- - - -
- element('Components/groupproperties', array( - 'group' => $groupergroupsdetail - )); ?> -
-
-
-

- element('Components/groupattributes', array( - 'attributes' => $groupergroupsdetail['attributes'], - 'baseUrl' => $attrUrlBase - )); ?> -
-
-
-
- \ No newline at end of file diff --git a/View/GrouperGroups/groupmember.ctp b/View/GrouperGroups/groupmember.ctp deleted file mode 100644 index 82428f8..0000000 --- a/View/GrouperGroups/groupmember.ctp +++ /dev/null @@ -1,33 +0,0 @@ - -extend('/GrouperGroups/base'); ?> -Html->addCrumb(_txt('pl.grouperlite.nav.memberships')); ?> - -element('GrouperLiteWidget.Components/vue-table', array( - 'groupData' => json_encode(array( - 'adhoc' => $groupmemberships, - 'working' => $wgmemberships - )), - 'treatAsOwner' => $isuserowner === 'T' ? 'true' : 'false', - 'columns' => json_encode(array( - array('value' => 'name', 'label' => _txt('pl.grouperlite.table.name')), - array('value' => 'role', 'label' => _txt('pl.grouperlite.table.role')), - array('value' => 'description', 'label' => _txt('pl.grouperlite.table.description')), - array('value' => 'action', 'label' => _txt('pl.grouperlite.table.action')), - )), - 'optAction' => "leavegroup", - 'actionUrl' => $this->Html->url([ - 'controller' => 'grouper_groups', - 'action' => 'leavegroup', - 'co' => $vv_coid, - 'glid' => $vv_config['CoGrouperLiteWidget']['id'], - ]), - 'members' => true, - 'addSubscribers' => false, -)); ?> - -
-

-
- - - \ No newline at end of file diff --git a/View/GrouperGroups/groupoptin.ctp b/View/GrouperGroups/groupoptin.ctp deleted file mode 100644 index e5cfa4d..0000000 --- a/View/GrouperGroups/groupoptin.ctp +++ /dev/null @@ -1,29 +0,0 @@ - -extend('/GrouperGroups/base'); ?> -Html->addCrumb(_txt('pl.grouperlite.nav.groups-can-join')); ?> -element('GrouperLiteWidget.Components/navigation-groups', array('active' => 'groupoptin')); ?> -element('GrouperLiteWidget.Components/search', array('active' => 'groupoptin')); ?> -element('GrouperLiteWidget.Components/vue-table', array( - 'groupData' => json_encode(array( - 'adhoc' => $groupoptins, - 'working' => array() - )), - 'treatAsOwner' => false, - 'columns' => json_encode(array( - array('value' => 'name', 'label' => _txt('pl.grouperlite.table.name')), - array('value' => 'description', 'label' => _txt('pl.grouperlite.table.description')), - array('value' => 'action', 'label' => _txt('pl.grouperlite.table.action')), - )), - 'optAction' => "joingroup", - 'actionUrl' => $this->Html->url([ - 'controller' => 'grouper_groups', - 'action' => 'joingroup', - 'co' => $vv_coid, - 'glid' => $vv_config['CoGrouperLiteWidget']['id'], - ]), -)); ?> - -
-

-
- \ No newline at end of file diff --git a/View/GrouperGroups/groupowner.ctp b/View/GrouperGroups/groupowner.ctp deleted file mode 100644 index 68deeec..0000000 --- a/View/GrouperGroups/groupowner.ctp +++ /dev/null @@ -1,37 +0,0 @@ - -extend('/GrouperGroups/base'); ?> -Html->addCrumb(_txt('pl.grouperlite.nav.groups-presided')); ?> -element('Components/navigation-groups', array('active' => 'groupowner')); ?> -element('Components/search', array('active' => 'groupowner')); ?> - 'name', 'label' => _txt('pl.grouperlite.table.name')), - array('value' => 'role', 'label' => _txt('pl.grouperlite.table.role')), - array('value' => 'description', 'label' => _txt('pl.grouperlite.table.description')), - array('value' => 'status', 'label' => _txt('pl.grouperlite.table.status')), - array('value' => 'action', 'label' => _txt('pl.grouperlite.table.action')), -); - -if ($vv_is_user_owner !== 'T') { - array_splice($columns, 1, 1); -} -?> - -element('GrouperLiteWidget.Components/vue-table', array( - 'groupData' => json_encode(array( - 'adhoc' => $groupsowners, - 'working' => array() - )), - 'treatAsOwner' => $vv_is_user_owner === 'T' ? 'true' : 'false', - 'columns' => json_encode($columns), - 'addSubscribers' => true, - 'members' => true -)); ?> - - - -
-

-
- - \ No newline at end of file diff --git a/View/GrouperGroups/joingroup.ctp b/View/GrouperGroups/joingroup.ctp deleted file mode 100644 index e4d9672..0000000 --- a/View/GrouperGroups/joingroup.ctp +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/View/GrouperGroups/leavegroup.ctp b/View/GrouperGroups/leavegroup.ctp deleted file mode 100644 index 2a3df51..0000000 --- a/View/GrouperGroups/leavegroup.ctp +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/View/GrouperGroups/templatefields.inc b/View/GrouperGroups/templatefields.inc deleted file mode 100644 index 5b4d338..0000000 --- a/View/GrouperGroups/templatefields.inc +++ /dev/null @@ -1,234 +0,0 @@ -
-
- Form->label(false, _txt('pl.grouperlite.form.template.work-group-extension.label'), array( - 'for' => 'gsh_input_workingGroupExtension', - 'class' => "col-sm-3 col-form-label d-flex align-items-center" - )); ?> -
- Form->input('gsh_input_workingGroupExtension', array( - 'label' => false, - 'class' => 'form-control', - 'required' => true, - 'maxlength' => '62', - 'id' => 'gsh_input_workingGroupExtension' - )); ?> -
-
-
- Form->label(false, _txt('pl.grouperlite.form.template.work-group-disp-extension.label'), array( - 'for' => 'gsh_input_workingGroupDisplayExtension', - 'class' => "col-sm-3 col-form-label d-flex align-items-center" - )); ?> -
- Form->input('gsh_input_workingGroupDisplayExtension', array( - 'label' => false, - 'class' => 'form-control', - 'maxlength' => '62', - 'id' => 'gsh_input_workingGroupDisplayExtension' - )); ?> -
-
-
- Form->label(false, _txt('pl.grouperlite.form.template.work-group-description.label'), array( - 'for' => 'gsh_input_workingGroupDescription', - 'class' => "col-sm-3 col-form-label d-flex align-items-center" - )); ?> -
- Form->input('gsh_input_workingGroupDescription', array( - 'label' => false, - 'class' => 'form-control', - 'maxlength' => '62', - 'id' => 'gsh_input_workingGroupDescription' - )); ?> -
-
-
- -
- Form->input( - 'gsh_input_isSympa', - array( - 'label' => true, - 'class' => 'form-check-input', - 'legend' => false, - 'id' => 'gsh_input_isSympa', - 'before' => '
', - 'separator' => '
', - 'after' => '
', - 'options' => array( - true => _txt('pl.grouperlite.form.template.value.positive'), - false => _txt('pl.grouperlite.form.template.value.negative') - ), - 'type' => 'radio', - 'default' => true - ) - ); ?> -
-
-
- -
- Form->input( - 'gsh_input_sympaDomain', - array( - 'label' => true, - 'class' => 'form-check-input', - 'legend' => false, - 'id' => 'gsh_input_sympaDomain', - 'before' => '
', - 'separator' => '
', - 'after' => '
', - 'options' => array( - 'internet2' => _txt('pl.grouperlite.form.template.value.internet2'), - 'incommon' => _txt('pl.grouperlite.form.template.value.incommon') - ), - 'type' => 'radio' - ) - ); ?> -
-
-
- -
- Form->input( - 'gsh_input_isSympaModerated', - array( - 'label' => true, - 'class' => 'form-check-input', - 'legend' => false, - 'id' => 'gsh_input_isSympaModerated', - 'before' => '
', - 'separator' => '
', - 'after' => '
', - 'options' => array( - true => _txt('pl.grouperlite.form.template.value.positive'), - false => _txt('pl.grouperlite.form.template.value.negative') - ), - 'type' => 'radio', - 'default' => false - ) - ); ?> -
-
-
- -
- Form->input( - 'gsh_input_isOptin', - array( - 'label' => true, - 'class' => 'form-check-input', - 'legend' => false, - 'id' => 'gsh_input_isOptin', - 'before' => '
', - 'separator' => '
', - 'after' => '
', - 'options' => array( - true => _txt('pl.grouperlite.form.template.value.positive'), - false => _txt('pl.grouperlite.form.template.value.negative') - ), - 'type' => 'radio', - 'default' => false - ) - ); ?> -
-
- -
- -
- Form->input( - 'gsh_input_isConfluence', - array( - 'label' => true, - 'class' => 'form-check-input', - 'legend' => false, - 'id' => 'gsh_input_isConfluence', - 'before' => '
', - 'separator' => '
', - 'after' => '
', - 'options' => array( - true => _txt('pl.grouperlite.form.template.value.positive'), - false => _txt('pl.grouperlite.form.template.value.negative') - ), - 'type' => 'radio', - 'default' => false - ) - ); ?> -
-
-
- -
- Form->input( - 'gsh_input_isJira', - array( - 'label' => true, - 'class' => 'form-check-input', - 'legend' => false, - 'id' => 'gsh_input_isJira', - 'before' => '
', - 'separator' => '
', - 'after' => '
', - 'options' => array( - true => _txt('pl.grouperlite.form.template.value.positive'), - false => _txt('pl.grouperlite.form.template.value.negative') - ), - 'type' => 'radio', - 'default' => false - ) - ); ?> -
-
-
-
- Form->button(_txt('pl.grouperlite.form.group.action.save'), array( - 'type' => 'submit', - 'class' => 'btn btn-grouper btn-primary btn-lg btn-raised', - 'id' => 'submit' - )); ?> -
-
-
- \ No newline at end of file diff --git a/View/GrouperGroups/users.json b/View/GrouperGroups/users.json deleted file mode 100644 index 6e08fee..0000000 --- a/View/GrouperGroups/users.json +++ /dev/null @@ -1,344 +0,0 @@ -[ - { - "name": "Galena Munoz", - "email": "ipsum.cursus.vestibulum@urnaconvalliserat.net", - "cou": "Active, Member", - "org": "Eu Corporation" - }, - { - "name": "Doris Santos", - "email": "vitae@non.org", - "cou": "Active, Filesource, Member", - "org": "Aliquam LLC" - }, - { - "name": "Alma English", - "email": "pede.Cum.sociis@telluseu.net", - "cou": "Member, Active, Writer, Filesource", - "org": "Consectetuer Corp." - }, - { - "name": "Halee Finley", - "email": "in.consectetuer.ipsum@parturientmontes.co.uk", - "cou": "Member", - "org": "Felis Ltd" - }, - { - "name": "Austin Hardy", - "email": "iaculis@Donecegestas.co.uk", - "cou": "Writer", - "org": "Nonummy LLP" - }, - { - "name": "Victor Powell", - "email": "nisi@eget.ca", - "cou": "Member, Active", - "org": "Praesent Interdum Ligula PC" - }, - { - "name": "Baxter Estrada", - "email": "in.consequat.enim@vestibulum.co.uk", - "cou": "", - "org": "In Molestie Tortor Corporation" - }, - { - "name": "Carlos Rose", - "email": "elit.elit@non.co.uk", - "cou": "Member, Writer, Filesource, Active", - "org": "Risus Nulla Eget LLP" - }, - { - "name": "Ishmael Patton", - "email": "leo.Morbi@mollisnoncursus.co.uk", - "cou": "Writer, Filesource", - "org": "A Enim Suspendisse Consulting" - }, - { - "name": "Ryder Tanner", - "email": "varius@rutrum.org", - "cou": "Writer, Filesource", - "org": "Ornare Elit Elit Consulting" - }, - { - "name": "Dane Rollins", - "email": "Curabitur.consequat.lectus@Naminterdum.ca", - "cou": "Member", - "org": "Ut Pellentesque Consulting" - }, - { - "name": "Brian Kaufman", - "email": "aliquet.vel@orciluctuset.edu", - "cou": "Filesource, Active", - "org": "Justo Eu Arcu Inc." - }, - { - "name": "Lester Price", - "email": "Integer@idlibero.edu", - "cou": "Writer, Member, Active, Filesource", - "org": "Dapibus Rutrum Justo Incorporated" - }, - { - "name": "Helen Cardenas", - "email": "Curabitur.egestas.nunc@quisdiam.org", - "cou": "Member, Active, Filesource", - "org": "Faucibus Morbi Vehicula Associates" - }, - { - "name": "Hop Holloway", - "email": "non.arcu.Vivamus@nibhvulputate.org", - "cou": "Filesource, Active, Member", - "org": "Nunc Sed Pede Ltd" - }, - { - "name": "Madeson Hendrix", - "email": "viverra@purus.com", - "cou": "", - "org": "Neque Venenatis Consulting" - }, - { - "name": "Amity Navarro", - "email": "tincidunt.Donec@inconsequat.com", - "cou": "Writer", - "org": "Pretium Aliquet Consulting" - }, - { - "name": "Cameron Booth", - "email": "nulla.magna@Quisqueporttitor.net", - "cou": "", - "org": "Fusce Associates" - }, - { - "name": "Sybil Burgess", - "email": "Fusce.feugiat@sem.org", - "cou": "Writer, Member, Filesource, Active", - "org": "Curabitur Corporation" - }, - { - "name": "Otto Cantrell", - "email": "Ut@nonarcu.org", - "cou": "", - "org": "Cursus PC" - }, - { - "name": "Mannix Obrien", - "email": "eu.neque@egestasDuisac.ca", - "cou": "", - "org": "Amet Faucibus Corp." - }, - { - "name": "Marcia Gill", - "email": "molestie.in.tempus@posuere.edu", - "cou": "", - "org": "Et Corp." - }, - { - "name": "Tanek Figueroa", - "email": "diam@arcuSed.org", - "cou": "Active", - "org": "Bibendum Donec Ltd" - }, - { - "name": "Ira Lynn", - "email": "aliquet@consequatpurusMaecenas.edu", - "cou": "Filesource", - "org": "Massa Suspendisse Eleifend Corporation" - }, - { - "name": "Lesley Pittman", - "email": "iaculis.odio@Phasellus.edu", - "cou": "Member, Writer", - "org": "Urna Et Arcu PC" - }, - { - "name": "Len Ryan", - "email": "ut.mi.Duis@sitamet.org", - "cou": "Writer, Filesource, Active", - "org": "Vestibulum Accumsan PC" - }, - { - "name": "Sarah Morton", - "email": "dis.parturient@rhoncusProin.org", - "cou": "Active", - "org": "Amet Consectetuer PC" - }, - { - "name": "Marny Hewitt", - "email": "dapibus@orciquis.ca", - "cou": "Active, Writer", - "org": "Ultricies Consulting" - }, - { - "name": "Abel Lyons", - "email": "Proin.eget.odio@enim.org", - "cou": "Writer", - "org": "Amet Lorem Corporation" - }, - { - "name": "Slade Schneider", - "email": "ante.dictum.cursus@turpisAliquamadipiscing.co.uk", - "cou": "", - "org": "Lacinia Company" - }, - { - "name": "Ashton Arnold", - "email": "velit.justo@velitin.edu", - "cou": "", - "org": "Diam Ltd" - }, - { - "name": "Phelan Goff", - "email": "ullamcorper.viverra.Maecenas@Donecporttitortellus.ca", - "cou": "", - "org": "Faucibus Id Libero Institute" - }, - { - "name": "Graham Underwood", - "email": "Nulla.dignissim.Maecenas@Donecsollicitudin.ca", - "cou": "Filesource, Member", - "org": "Duis Gravida Praesent Ltd" - }, - { - "name": "Alisa Scott", - "email": "ipsum.primis.in@erat.co.uk", - "cou": "Writer, Active", - "org": "Vivamus Non Lorem Inc." - }, - { - "name": "Asher Mccray", - "email": "semper.auctor@nibhAliquamornare.edu", - "cou": "Writer, Member", - "org": "Orci Corporation" - }, - { - "name": "Hanna Salas", - "email": "feugiat@purusactellus.net", - "cou": "", - "org": "Lectus Convallis Industries" - }, - { - "name": "Jeremy Riley", - "email": "Ut.sagittis@necante.ca", - "cou": "Writer", - "org": "Luctus Curabitur LLP" - }, - { - "name": "Violet Trevino", - "email": "vestibulum@nec.edu", - "cou": "", - "org": "Ligula Aenean Gravida Consulting" - }, - { - "name": "Carissa Myers", - "email": "non@rhoncusDonec.co.uk", - "cou": "Member, Filesource", - "org": "Tempor Corporation" - }, - { - "name": "Kadeem Osborn", - "email": "nulla.In@aliquameros.ca", - "cou": "Filesource, Active, Member, Writer", - "org": "Tempus Risus Donec PC" - }, - { - "name": "Kiayada England", - "email": "ultrices.posuere@lectus.net", - "cou": "", - "org": "Orci Institute" - }, - { - "name": "Connor Gardner", - "email": "diam.eu@tortorIntegeraliquam.org", - "cou": "Member, Active, Writer", - "org": "Vulputate Nisi Sem LLP" - }, - { - "name": "Alfonso Casey", - "email": "montes.nascetur.ridiculus@risus.net", - "cou": "Member", - "org": "Sed Eu Nibh Consulting" - }, - { - "name": "Avye Raymond", - "email": "mauris.sapien@tempusrisusDonec.co.uk", - "cou": "Active, Writer, Filesource, Member", - "org": "Amet Ante Incorporated" - }, - { - "name": "Quamar Cross", - "email": "elit.pretium@Sedid.org", - "cou": "", - "org": "Dapibus PC" - }, - { - "name": "Justine Kemp", - "email": "diam@ornare.org", - "cou": "Writer, Filesource", - "org": "Libero At Auctor Associates" - }, - { - "name": "Blair Rush", - "email": "at.velit.Cras@pulvinar.ca", - "cou": "", - "org": "Nulla Tempor Associates" - }, - { - "name": "Jael Travis", - "email": "ut.dolor.dapibus@vestibulumneceuismod.ca", - "cou": "", - "org": "Facilisis Lorem LLC" - }, - { - "name": "Donovan Patel", - "email": "bibendum.ullamcorper@Sedeu.co.uk", - "cou": "Member, Filesource, Writer, Active", - "org": "Ipsum Sodales Incorporated" - }, - { - "name": "Sylvester Brady", - "email": "amet@consequatauctornunc.net", - "cou": "", - "org": "Semper Nam Limited" - }, - { - "name": "Yuri Eaton", - "email": "Nullam.lobortis.quam@Etiamligulatortor.com", - "cou": "", - "org": "Eu Elit Nulla LLP" - }, - { - "name": "Bree Harmon", - "email": "libero.Integer@maurisaliquam.ca", - "cou": "Filesource, Active, Member, Writer", - "org": "Dui In Sodales PC" - }, - { - "name": "Tanek Tucker", - "email": "arcu@Craslorem.org", - "cou": "Active, Member, Filesource", - "org": "Nisl Arcu Company" - }, - { - "name": "Jermaine Stevens", - "email": "luctus@Donec.org", - "cou": "Active, Writer", - "org": "Pellentesque Sed Dictum Corp." - }, - { - "name": "Ira Robinson", - "email": "malesuada.vel@lobortisClass.ca", - "cou": "Active, Member", - "org": "Ac Sem Ut Consulting" - }, - { - "name": "Jocelyn Fulton", - "email": "tristique.senectus.et@faucibusorci.co.uk", - "cou": "Active, Member, Filesource, Writer", - "org": "Ante Ipsum Institute" - }, - { - "name": "Tatyana Kelly", - "email": "a.facilisis.non@tortorat.ca", - "cou": "", - "org": "Donec Feugiat Limited" - } -] \ No newline at end of file diff --git a/View/Layouts/Emails/html/default.ctp b/View/Layouts/Emails/html/default.ctp deleted file mode 100644 index 747994a..0000000 --- a/View/Layouts/Emails/html/default.ctp +++ /dev/null @@ -1,27 +0,0 @@ - - - - - <?php echo $this->fetch('title'); ?> - - - fetch('content'); ?> - -

This email was sent using the CakePHP Framework

- - \ No newline at end of file diff --git a/View/Layouts/Emails/text/default.ctp b/View/Layouts/Emails/text/default.ctp deleted file mode 100644 index 82a1e19..0000000 --- a/View/Layouts/Emails/text/default.ctp +++ /dev/null @@ -1,19 +0,0 @@ - -fetch('content'); ?> - -This email was sent using the CakePHP Framework, https://cakephp.org. diff --git a/View/Layouts/rss/default.ctp b/View/Layouts/rss/default.ctp deleted file mode 100644 index 60a5365..0000000 --- a/View/Layouts/rss/default.ctp +++ /dev/null @@ -1,13 +0,0 @@ -fetch('title'); -endif; - -echo $this->Rss->document( - $this->Rss->channel( - array(), $channel, $this->fetch('content') - ) -); diff --git a/View/Layouts/xml/default.ctp b/View/Layouts/xml/default.ctp deleted file mode 100644 index fbd5ee0..0000000 --- a/View/Layouts/xml/default.ctp +++ /dev/null @@ -1 +0,0 @@ -fetch('content'); ?> From 73cb251db7ad884edd55e98ec63e1055225d754c Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Fri, 1 Mar 2024 11:00:38 +0200 Subject: [PATCH 21/60] restore security for actions --- Controller/GrouperGroupsController.php | 11 ----------- Lib/GrouperApiAccess.php | 8 ++++++-- Model/GrouperGroup.php | 22 +++++++++++----------- 3 files changed, 17 insertions(+), 24 deletions(-) diff --git a/Controller/GrouperGroupsController.php b/Controller/GrouperGroupsController.php index d7bb129..68b8ed5 100644 --- a/Controller/GrouperGroupsController.php +++ b/Controller/GrouperGroupsController.php @@ -106,18 +106,7 @@ public function beforeFilter() HttpStatusCodesEnum::HTTP_BAD_REQUEST); } - $this->Security->unlockedActions = array( - 'removeSubscriber', - 'addSubscriber', - 'joinGroup', - 'leaveGroup', - 'groupMember', - 'groupOptin', - 'groupOwner' - ); - if ($this->request->is('ajax')) { - $this->response->disableCache(); $this->RequestHandler->addInputType('json', array('json_decode', true)); } diff --git a/Lib/GrouperApiAccess.php b/Lib/GrouperApiAccess.php index c1750fd..3eb5c40 100644 --- a/Lib/GrouperApiAccess.php +++ b/Lib/GrouperApiAccess.php @@ -371,6 +371,8 @@ public function getGroupInfo(string $groupName): array /** * Returns all the groups the active user is a member of, that they are allowed to see. * + * THIS REQUEST TARGETS THE GROUPS ENDPOINT + * * Note: Params added at end make sure that the groups returned can only be viewed by the member logged into * Grouper Lite * @@ -381,7 +383,7 @@ public function getGroupInfo(string $groupName): array * * @throws GrouperLiteWidgetException */ - public function getUserGroupMemberships(string $actorUserId, string $userId): array + public function getUserGroups(string $actorUserId, string $userId): array { if(empty($userId)) { return []; @@ -404,6 +406,8 @@ public function getUserGroupMemberships(string $actorUserId, string $userId): ar * Returns either the groups the user is able to Opt into or can manage the memberships of. * Used for requests made to Membership endpoint in Grouper WS * + * THIS REQUEST TARGETS THE MEMBERSHIP ENDPOINT + * * @param string $userId * @param string $actAsUserId * @param string $groupType @@ -439,7 +443,7 @@ public function getUserGroupMemberships(string $actorUserId, string $userId): ar * } * } */ - public function getGrouperUserMemberships(string $userId, string $actAsUserId, string $groupType): array + public function getUserMemberships(string $userId, string $actAsUserId, string $groupType): array { if(!in_array($groupType, [ GrouperGroupTypeEnum::OPTINS, diff --git a/Model/GrouperGroup.php b/Model/GrouperGroup.php index 97c22de..5420743 100644 --- a/Model/GrouperGroup.php +++ b/Model/GrouperGroup.php @@ -79,8 +79,8 @@ public function isUserGroupOwner(string $userId, array $cfg): bool } try { - $resultsAdmin = $this->grouperAPI->getGrouperUserMemberships($userId, $userId, GrouperGroupTypeEnum::ADMIN); - $resultsUpdate = $this->grouperAPI->getGrouperUserMemberships($userId, $userId, GrouperGroupTypeEnum::UPDATE); + $resultsAdmin = $this->grouperAPI->getUserMemberships($userId, $userId, GrouperGroupTypeEnum::ADMIN); + $resultsUpdate = $this->grouperAPI->getUserMemberships($userId, $userId, GrouperGroupTypeEnum::UPDATE); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; @@ -144,7 +144,7 @@ public function filteredMemberOfGroups(string $userId, array $cfg): array try { $memberOfGroups = $this->memberOfGroups($userId, $userId, $cfg); // Determine which groups can be left by user, if wanted. - $optOutGroups = $this->grouperAPI->getGrouperUserMemberships($userId, $userId, GrouperGroupTypeEnum::OPTOUTS); + $optOutGroups = $this->grouperAPI->getUserMemberships($userId, $userId, GrouperGroupTypeEnum::OPTOUTS); $optOutGroupsNames = Hash::combine($optOutGroups, '{n}.name', '{n}.displayExtension'); foreach ($memberOfGroups as &$memberOfGroup) { @@ -282,7 +282,7 @@ private function memberOfGroups(string $actorUserId, string $userId, array $cfg) $this->initApi($cfg); try { - return $this->grouperAPI->getUserGroupMemberships($actorUserId, $userId); + return $this->grouperAPI->getUserGroups($actorUserId, $userId); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; @@ -308,8 +308,8 @@ public function getOwnedGroups(string $userId, array $cfg): array $this->initApi($cfg); try { - $resultsAdmin = $this->grouperAPI->getGrouperUserMemberships($userId, $userId, GrouperGroupTypeEnum::ADMIN); - $resultsUpdate = $this->grouperAPI->getGrouperUserMemberships($userId, $userId, GrouperGroupTypeEnum::UPDATE); + $resultsAdmin = $this->grouperAPI->getUserMemberships($userId, $userId, GrouperGroupTypeEnum::ADMIN); + $resultsUpdate = $this->grouperAPI->getUserMemberships($userId, $userId, GrouperGroupTypeEnum::UPDATE); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; @@ -331,7 +331,7 @@ public function getOwnedGroups(string $userId, array $cfg): array public function getOwnedStems(string $userId): array { try { - return $this->grouperAPI->getGrouperUserMemberships($userId, $userId, GrouperGroupTypeEnum::ADMIN); + return $this->grouperAPI->getUserMemberships($userId, $userId, GrouperGroupTypeEnum::ADMIN); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; @@ -439,9 +439,9 @@ public function optinGroups(string $userId, array $cfg): array try { // Groups the user can join or leave - $joinOrLeave = $this->grouperAPI->getGrouperUserMemberships($userId, - $userId, - GrouperGroupTypeEnum::OPTINS); + $joinOrLeave = $this->grouperAPI->getUserMemberships($userId, + $userId, + GrouperGroupTypeEnum::OPTINS); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; @@ -449,7 +449,7 @@ public function optinGroups(string $userId, array $cfg): array try { // Groups the user is a member of - $userGroups = $this->grouperAPI->getUserGroupMemberships($userId, $userId); + $userGroups = $this->grouperAPI->getUserGroups($userId, $userId); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; From 93ee86fa64ddb9a9da882da3049feef3dc5d634d Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Tue, 5 Mar 2024 20:18:31 +0200 Subject: [PATCH 22/60] revert security unlocked actions --- Controller/GrouperGroupsController.php | 16 +++++++++--- Lib/GrouperApiAccess.php | 35 +++++++++++++++++++------- webroot/js/groups.js | 1 - 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/Controller/GrouperGroupsController.php b/Controller/GrouperGroupsController.php index 68b8ed5..f4c65cf 100644 --- a/Controller/GrouperGroupsController.php +++ b/Controller/GrouperGroupsController.php @@ -105,10 +105,18 @@ public function beforeFilter() throw new InvalidArgumentException(_txt('er.grouperlite.glid'), HttpStatusCodesEnum::HTTP_BAD_REQUEST); } - - if ($this->request->is('ajax')) { - $this->RequestHandler->addInputType('json', array('json_decode', true)); - } + $this->response->disableCache(); + $this->RequestHandler->addInputType('json', array('json_decode', true)); + + $this->Security->unlockedActions = [ + 'removeSubscriber', + 'addSubscriber', + 'joinGroup', + 'leaveGroup', + 'groupMember', + 'groupOptin', + 'groupOwner' + ]; // Get the config $args = array(); diff --git a/Lib/GrouperApiAccess.php b/Lib/GrouperApiAccess.php index 3eb5c40..6f77fa9 100644 --- a/Lib/GrouperApiAccess.php +++ b/Lib/GrouperApiAccess.php @@ -332,6 +332,9 @@ public function deleteGroupWithTemplate(string $actAsUserId, string $workingGrou */ public function getGroupInfo(string $groupName): array { + if(empty($groupName)) { + return []; + } $groupInfo = []; //Build request logic @@ -362,7 +365,7 @@ public function getGroupInfo(string $groupName): array $groupInfo = $results['WsGetAttributeAssignmentsResults']['wsGroups']; } - //Now get the Group Attributes and add them to group + // Now get the Group Attributes and add them to group $groupInfo[0]['attributes'] = $results['WsGetAttributeAssignmentsResults']['wsAttributeAssigns'] ?? []; return $groupInfo; @@ -376,22 +379,22 @@ public function getGroupInfo(string $groupName): array * Note: Params added at end make sure that the groups returned can only be viewed by the member logged into * Grouper Lite * - * @param string $actorUserId + * @param string $actAsUserId * @param string $userId * * @return array Membership records that User is a member of in Grouper * * @throws GrouperLiteWidgetException */ - public function getUserGroups(string $actorUserId, string $userId): array + public function getUserGroups(string $actAsUserId, string $userId): array { - if(empty($userId)) { + if(empty($userId) || empty($actAsUserId)) { return []; } $actionEndpoint = "/subjects/{$userId}/groups?" . 'wsLiteObjectType=WsRestGetGroupsLiteRequest' - . "&actAsSubjectId={$actorUserId}"; + . "&actAsSubjectId={$actAsUserId}"; try { $results = $this->http->sendRequest('GET', $actionEndpoint); } catch (Exception $e) { @@ -445,6 +448,13 @@ public function getUserGroups(string $actorUserId, string $userId): array */ public function getUserMemberships(string $userId, string $actAsUserId, string $groupType): array { + if(empty($actAsUserId) + || empty($userId) + || empty($groupType) + ) { + return []; + } + if(!in_array($groupType, [ GrouperGroupTypeEnum::OPTINS, GrouperGroupTypeEnum::OPTOUTS, @@ -496,11 +506,12 @@ public function getUserMemberships(string $userId, string $actAsUserId, string $ /** * Get members associated to a specific Grouper Group * - * @param string $actorUserId + * @param string $actAsUserId * @param string $groupName * * @return array Listing of Members belonging to Grouper Group - * @throws GrouperLiteWidgetException|JsonException|NotFoundException + * @throws GrouperLiteWidgetException + * @throws JsonException * @example https://github.com/Internet2/grouper/blob/master/grouper-ws/grouper-ws/doc/samples/getGroups/WsSampleGetGroupsRest_json.txt * * $: > grouperClientAlias --debug=true --operation=getMembersWs --actAsSubjectId=john.b.doe@at.internet2.edu --subjectAttributeNames=name --groupNames=ref:incommon-collab:co:member @@ -532,13 +543,19 @@ public function getUserMemberships(string $userId, string $actAsUserId, string $ * } * } */ - public function getGroupMembers(string $actorUserId, string $groupName): array + public function getGroupMembers(string $actAsUserId, string $groupName): array { + if(empty($actAsUserId) + || empty($groupName) + ) { + return []; + } + //Build request logic $usersToShow = [ 'WsRestGetMembersRequest' => [ 'actAsSubjectLookup' => [ - 'subjectId' => $actorUserId + 'subjectId' => $actAsUserId ], 'wsGroupLookups' => [ ['groupName' => $groupName] diff --git a/webroot/js/groups.js b/webroot/js/groups.js index c4cf3a4..dae157a 100644 --- a/webroot/js/groups.js +++ b/webroot/js/groups.js @@ -42,7 +42,6 @@ export default { const resp = await fetch(`${apiPath}`, { headers: { "Accept": "application/json", - // 'Content-Type': 'application/x-www-form-urlencoded', }, method: "GET" }); From f2774dda1564974689db437131e26c1ae585e5af Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Wed, 6 Mar 2024 18:17:27 +0200 Subject: [PATCH 23/60] Fix buggy optin list of groups --- Controller/GrouperGroupsController.php | 6 ++++++ Model/GrouperGroup.php | 12 +++++++++++- View/GrouperGroups/index.ctp | 27 +++----------------------- webroot/js/groups.js | 3 +-- 4 files changed, 21 insertions(+), 27 deletions(-) diff --git a/Controller/GrouperGroupsController.php b/Controller/GrouperGroupsController.php index f4c65cf..76bedd8 100644 --- a/Controller/GrouperGroupsController.php +++ b/Controller/GrouperGroupsController.php @@ -174,6 +174,8 @@ public function findSubscriber(): void } /** + * GroupMember vue route for rendering groupmemberapi results + * * @return void */ public function groupMember(): void @@ -182,6 +184,8 @@ public function groupMember(): void } /** + * GroupOptin vue route for rendering groupoptinapi results + * * @return void */ public function groupOptin(): void @@ -190,6 +194,8 @@ public function groupOptin(): void } /** + * GroupOwner vue route for rendering groupownerapi results + * * @return void */ public function groupOwner(): void diff --git a/Model/GrouperGroup.php b/Model/GrouperGroup.php index 5420743..49172c6 100644 --- a/Model/GrouperGroup.php +++ b/Model/GrouperGroup.php @@ -455,10 +455,20 @@ public function optinGroups(string $userId, array $cfg): array throw $e; } + // I am currently not a member to any Group. Return everything + if(empty($userGroups)) { + return $joinOrLeave; + } + // Extract the names of the Groups the user is a member of $userGroupsNames = Hash::extract($userGroups, '{n}.name'); // Return the groups the user can join and is not a member of - return array_filter($joinOrLeave, static fn($value) => !in_array($value['name'], $userGroupsNames)); + return array_values( // Restart indexing from 0(zero) on the final array + array_filter( // Return the groups I am currently not a member + $joinOrLeave, + static fn($value) => !in_array($value['name'], $userGroupsNames) + ) + ); } /** diff --git a/View/GrouperGroups/index.ctp b/View/GrouperGroups/index.ctp index b05e647..48ed270 100644 --- a/View/GrouperGroups/index.ctp +++ b/View/GrouperGroups/index.ctp @@ -117,30 +117,9 @@ glid: , mode: "", base: "webroot ?>grouper_lite_widget/grouper_groups", - find: "Html->url( - array( - 'controller' => 'grouper_groups', - 'action' => 'findSubscriber', - 'co' => $vv_coid, - 'glid' => $vv_config['CoGrouperLiteWidget']['id'], - ) - ) ?>", - join: "Html->url( - array( - 'controller' => 'grouper_groups', - 'action' => 'joinGroup', - 'co' => $vv_coid, - 'glid' => $vv_config['CoGrouperLiteWidget']['id'], - ) - ) ?>", - leave: "Html->url( - array( - 'controller' => 'grouper_groups', - 'action' => 'leaveGroup', - 'co' => $vv_coid, - 'glid' => $vv_config['CoGrouperLiteWidget']['id'], - ) - ) ?>", + find: "webroot ?>grouper_lite_widget/grouper_groups/findSubscriber/co:/glid:", + join: "webroot ?>grouper_lite_widget/grouper_groups/joinGroup/co:/glid:", + leave: "webroot ?>grouper_lite_widget/grouper_groups/leaveGroup/co:/glid:", remove: "webroot ?>grouper_lite_widget/grouper_groups/removeSubscriber/co:/glid:", add: "webroot ?>grouper_lite_widget/grouper_groups/addSubscriber/co:/glid:", group: "webroot ?>grouper_lite_widget/grouper_groups/groupSubscribers/co:/glid:", diff --git a/webroot/js/groups.js b/webroot/js/groups.js index dae157a..35f910d 100644 --- a/webroot/js/groups.js +++ b/webroot/js/groups.js @@ -57,8 +57,7 @@ export default { group.loading = true; const resp = await fetch(`${this.api.leave}?GroupName=${group.name}&GroupDisplayName=${group.displayName}`, { headers: { - "Accept": "application/json", - // 'Content-Type': 'application/x-www-form-urlencoded', + "Accept": "application/json" }, method: "DELETE" }); From 70d448096344d8bf355e46a35f1d9888d10ffcba Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Wed, 6 Mar 2024 20:36:49 +0200 Subject: [PATCH 24/60] Add the Users i manage tab --- Controller/GrouperGroupsController.php | 10 ++++++++++ View/CoGrouperLiteWidgets/display.ctp | 12 ++++++++++++ View/GrouperGroups/index.ctp | 6 ++++++ webroot/js/grouper-groups-view.js | 4 ++++ 4 files changed, 32 insertions(+) diff --git a/Controller/GrouperGroupsController.php b/Controller/GrouperGroupsController.php index 76bedd8..ae3e336 100644 --- a/Controller/GrouperGroupsController.php +++ b/Controller/GrouperGroupsController.php @@ -596,6 +596,16 @@ public function restResponse(int $status, $this->response->send(); } + /** + * UserManager vue route for rendering + * + * @return void + */ + public function userManager(): void + { + $this->render('index'); + } + /** * Override the default sanity check performed in AppController * diff --git a/View/CoGrouperLiteWidgets/display.ctp b/View/CoGrouperLiteWidgets/display.ctp index ccf0c76..8cb6201 100644 --- a/View/CoGrouperLiteWidgets/display.ctp +++ b/View/CoGrouperLiteWidgets/display.ctp @@ -91,6 +91,18 @@ $idsuffix = rand(); 'glid' => $glid ) ); ?>" + }, + { + label: "", + url: "Html->url( + array( + 'plugin' => $pl, + 'controller' => 'grouper_groups', + 'action' => 'usermanager', + 'co' => $coid, + 'glid' => $glid + ) + ); ?>" } ] } diff --git a/View/GrouperGroups/index.ctp b/View/GrouperGroups/index.ctp index 48ed270..376f9e3 100644 --- a/View/GrouperGroups/index.ctp +++ b/View/GrouperGroups/index.ctp @@ -14,6 +14,7 @@ import GroupMember from 'webroot ?>grouper_lite_widget/js/page/GroupMember.js'; import GroupOptin from 'webroot ?>grouper_lite_widget/js/page/GroupOptin.js'; import GroupOwner from 'webroot ?>grouper_lite_widget/js/page/GroupOwner.js'; + import UserManager from 'webroot ?>grouper_lite_widget/js/page/UserManager.js'; ", optin: "", owner: "", + manager: "", }, columns: _txt('pl.grouperlite.table.name'), diff --git a/webroot/js/grouper-groups-view.js b/webroot/js/grouper-groups-view.js index e2a508d..7d75678 100644 --- a/webroot/js/grouper-groups-view.js +++ b/webroot/js/grouper-groups-view.js @@ -25,6 +25,10 @@ export default { { path: `/groupowner/co:${this.api.co}/glid:${this.api.glid}`, label: this.txt.tabs.owner + }, + { + path: `/usermanager/co:${this.api.co}/glid:${this.api.glid}`, + label: this.txt.tabs.manager } ] } From a3da55edac9e2dd942866cd91675e79a8de36ef4 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Thu, 7 Mar 2024 16:56:29 +0200 Subject: [PATCH 25/60] Make autocomplete component fully dynamic --- webroot/js/autocomplete.js | 49 ++++++++++++++++++++++++++------------ webroot/js/members.js | 2 +- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/webroot/js/autocomplete.js b/webroot/js/autocomplete.js index a0a5958..978bd08 100644 --- a/webroot/js/autocomplete.js +++ b/webroot/js/autocomplete.js @@ -5,6 +5,14 @@ export default { group: { type: String, default: "" + }, + action: { + type: String, + default: "addUser" + }, + icon: { + type: String, + default: 'add' } }, inject: ['txt', 'api'], @@ -16,8 +24,18 @@ export default { }; }, methods: { - addUser() { - this.$emit('add', this.item); + performAction() { + this.$emit(this.action, this.item); + }, + toKebabCase(str) { + console.log('to kebab') + console.log('str', str) + return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); + } + }, + computed: { + btnTxt() { + return eval(`this.txt.${this.action}`) } }, mounted(el) { @@ -27,20 +45,21 @@ export default { minLength: 3, maxShowItems: 15, focus: ( event, ui ) => { - $("#grouper-add-member-search-container .co-loading-mini").hide(); + $("#grouper-search-container .co-loading-mini").hide(); return false; }, open: function (event, ui) { - $("#grouper-add-member-search-container .co-loading-mini").hide(); + $("#grouper-search-container .co-loading-mini").hide(); }, - create: function (event, ui) { - $("#add-user-input").focus(); + create: (event, ui) => { + // debugger + $(`#${this.toKebabCase(this.action)}-input`).focus(); }, close: function (event, ui) { - $("#grouper-add-member-search-container .co-loading-mini").hide(); + $("#grouper-search-container .co-loading-mini").hide(); }, search: function (event, ui) { - $("#grouper-add-member-search-container .co-loading-mini").show(); + $("#grouper-search-container .co-loading-mini").show(); }, // XXX We need access to the parent data object. // As a result we have to use arrow function syntax (ES6) @@ -48,15 +67,15 @@ export default { this.val = ui.item.identifier; this.item = ui.item; this.search = `${ui.item.label} (${ui.item.value})`; - $("#addUserbutton").prop('disabled', false).focus(); + $(`#${this.toKebabCase(this.action)}-btn`).prop('disabled', false).focus(); return false; }, }).autocomplete( "instance" )._renderItem = formatCoPersonAutoselectItem; }, template: /*html*/` -
+
-
-
diff --git a/webroot/js/members.js b/webroot/js/members.js index 2fa0a09..4900d03 100644 --- a/webroot/js/members.js +++ b/webroot/js/members.js @@ -163,7 +163,7 @@ export default {
- +
From a7e076f6e9986720ccc5e09a644a17be35ce21dd Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Thu, 7 Mar 2024 17:12:51 +0200 Subject: [PATCH 26/60] add people picker element --- Controller/GrouperGroupsController.php | 1 + Lib/lang.php | 2 +- View/GrouperGroups/index.ctp | 1 - webroot/js/autocomplete.js | 6 ++---- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Controller/GrouperGroupsController.php b/Controller/GrouperGroupsController.php index ae3e336..7800d23 100644 --- a/Controller/GrouperGroupsController.php +++ b/Controller/GrouperGroupsController.php @@ -439,6 +439,7 @@ function isAuthorized(): array|bool $p['groupSubscribers'] = true; $p['addSubscriber'] = true; $p['findSubscriber'] = true; + $p['usermanager'] = true; $p['removeSubscriber'] = true; $p['groupCreate'] = true; diff --git a/Lib/lang.php b/Lib/lang.php index 34bad66..43afa4e 100644 --- a/Lib/lang.php +++ b/Lib/lang.php @@ -163,7 +163,7 @@ 'pl.grouperlite.form.template.value.positive' => 'Yes', 'pl.grouperlite.form.template.value.negative' => 'No', - 'pl.grouperlite.search.tags.text' => 'Search', + 'pl.grouperlite.title.search' => 'Search', 'pl.grouperlite.pagination.counter' => 'Viewing {:start}-{:end} of {:count}', 'pl.grouperlite.attributes.zero-state' => 'No Attributes Associated to this Group.', diff --git a/View/GrouperGroups/index.ctp b/View/GrouperGroups/index.ctp index 376f9e3..f22b2e9 100644 --- a/View/GrouperGroups/index.ctp +++ b/View/GrouperGroups/index.ctp @@ -92,7 +92,6 @@ removeSubscriberError: "", removeSubscriberSuccess: "", getSubscriberError: "", - searchTag: "", peoplePickerPlaceHolder: "", noaccess: "", empty: "", diff --git a/webroot/js/autocomplete.js b/webroot/js/autocomplete.js index 978bd08..8fb8a54 100644 --- a/webroot/js/autocomplete.js +++ b/webroot/js/autocomplete.js @@ -28,18 +28,16 @@ export default { this.$emit(this.action, this.item); }, toKebabCase(str) { - console.log('to kebab') - console.log('str', str) return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); } }, computed: { btnTxt() { - return eval(`this.txt.${this.action}`) + return eval(`this.txt.${this.action}`) ?? eval(`this.txt.${this.icon}`) } }, mounted(el) { - const input = $(this.$el).find('#add-user-input'); + const input = $(this.$el).find(`#${this.toKebabCase(this.action)}-input`); input.autocomplete({ source: `${this.api.find}?co=${this.api.co}&mode=${this.api.mode}`, minLength: 3, From d2fe650d8d637bdd914cabbed22893f3156fd049 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Thu, 7 Mar 2024 17:13:04 +0200 Subject: [PATCH 27/60] add user manager view --- webroot/js/page/UserManager.js | 70 ++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 webroot/js/page/UserManager.js diff --git a/webroot/js/page/UserManager.js b/webroot/js/page/UserManager.js new file mode 100644 index 0000000..2c14fb8 --- /dev/null +++ b/webroot/js/page/UserManager.js @@ -0,0 +1,70 @@ +import Groups from '../groups.js'; +import PageCount from '../pagecount.js'; +import Pagination from '../pagination.js'; +import GroupsTable from '../groups-table.js'; +import Members from '../members.js'; +import Autocomplete from '../autocomplete.js'; +import Loader from '../loader.js'; + +export default { + components: { + Groups, + PageCount, + Pagination, + GroupsTable, + Members, + Autocomplete + }, + inject: ['api', 'txt'], + methods: { + showSubscribers(group) { + this.$refs.members.show(group); + }, + async findUserMemberships(user) { + const { identifier: id, label } = user; + this.loading = true; + const { displayExtension, name } = this.group; + const formData = new FormData(); + formData.append("userId", id); + formData.append("group", name); + const resp = await fetch(`${this.api.add}?group=${name}&userId=${id}`, { + method: "POST", + headers: { + "Accept": "application/json", + }, + body: formData + }); + if (resp.ok) { + await this.loadGroupSubscribers(this.group); + generateFlash(`${label} ${this.txt.findUserMembershipsSuccess} ${(displayExtension)}`, 'success'); + } else { + generateFlash(`${this.txt.findUserMembershipsError}`, 'error'); + let errorResponse = await resp.json(); + generateFlash(`${errorResponse.message}`, 'error'); + } + + this.loading = false; + } + }, + template: /*html*/` + + + + + + + + + + + + + + + + + ` +} \ No newline at end of file From 83727b0cca1a53bb024d77b35c02423302fab80d Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Thu, 7 Mar 2024 21:44:56 +0200 Subject: [PATCH 28/60] Backend work, frontend loader and disable action button. --- Controller/GrouperGroupsController.php | 101 ++++++++++++++++++------- Lib/lang.php | 1 + Model/GrouperGroup.php | 49 ++++++++++++ View/GrouperGroups/index.ctp | 2 + webroot/js/autocomplete.js | 10 +-- webroot/js/members.js | 2 +- webroot/js/page/UserManager.js | 50 +++++------- 7 files changed, 150 insertions(+), 65 deletions(-) diff --git a/Controller/GrouperGroupsController.php b/Controller/GrouperGroupsController.php index 7800d23..1657acd 100644 --- a/Controller/GrouperGroupsController.php +++ b/Controller/GrouperGroupsController.php @@ -40,7 +40,7 @@ class GrouperGroupsController extends GrouperLiteWidgetAppController public $helpers = array('Html', 'Form', 'Flash'); // Dynamic properties are deprecated, so we will define the property here - protected $userId = null; + private $userId = null; public $uses = array( 'GrouperLiteWidget.GrouperGroup', @@ -146,6 +146,14 @@ public function beforeRender() { $this->set('vv_coid', $this->cur_co['Co']['id']); } + /** + * @return null + */ + public function getUserId() + { + return $this->userId; + } + /** * Perform a "keyword" search for CO People, sort of like the CO Dashboard * cross controller search, but intended specifically for "people finder" @@ -234,7 +242,7 @@ public function groupSubscribers(): void /** * Listing of all Grouper Groups owned/admin by User Or search those Grouper Groups */ - public function groupOwnerApi() { + public function groupOwnerApi(): void { //Set initial setting $arguments = [ 'userId' => $this->userId, @@ -261,7 +269,7 @@ public function groupOwnerApi() { CakeLog::write('error', __METHOD__ . "::{$errorHint}: " . var_export($e->getMessage(), true)); $this->restResponse(HttpStatusCodesEnum::HTTP_INTERNAL_SERVER_ERROR, ErrorsEnum::Exception); - $this->set('groupsowners', []); + $this->set('groupowners', []); $this->Flash->set(_txt('pl.grouperlite.message.flash.owner-group-failed'), array('key' => 'error')); return; } @@ -275,7 +283,7 @@ public function groupOwnerApi() { * This includes self-joined Optin Groups, as well as required Groups User cannot leave * */ - public function groupMemberApi() { + public function groupMemberApi(): void { //Set initial setting $arguments = [ 'userId' => $this->userId, @@ -404,7 +412,7 @@ public function index(): void * @return array|bool Permissions * @since COmanage Registry v3.2.0 */ - function isAuthorized(): array|bool + public function isAuthorized(): array|bool { $roles = $this->Role->calculateCMRoles(); $cfg = $this->CoGrouperLiteWidget->getConfig(); @@ -420,32 +428,32 @@ function isAuthorized(): array|bool && is_array($identifiers) && isset($identifiers['Identifier']['identifier']) ) { - $this->userId = $identifiers['Identifier']['identifier']; + $this->setUserId($identifiers['Identifier']['identifier']); } // Determine what operations this user can perform // Construct the permission set for this user, which will also be passed to the view. - //Note: Leaving in current format, in case need to restrict certain pages, can just remove true and add params. - $p = array(); - - $p['index'] = true; - $p['groupowner'] = true; - $p['groupownerapi'] = true; - $p['groupoptin'] = true; - $p['groupoptinapi'] = true; - $p['groupmember'] = true; - $p['groupmemberapi'] = true; - $p['getBaseConfig'] = true; - $p['groupSubscribers'] = true; - $p['addSubscriber'] = true; - $p['findSubscriber'] = true; - $p['usermanager'] = true; - $p['removeSubscriber'] = true; - - $p['groupCreate'] = true; - $p['joinGroup'] = true; - $p['leaveGroup'] = true; - $p['groupcreatetemplate'] = true; + $p = []; + + $p['index'] = ($roles['cmadmin'] || $roles['coadmin'] || $roles['comember']); + $p['groupowner'] = ($roles['cmadmin'] || $roles['coadmin'] || $roles['comember']); + $p['groupownerapi'] = ($roles['cmadmin'] || $roles['coadmin'] || $roles['comember']); + $p['groupoptin'] = ($roles['cmadmin'] || $roles['coadmin'] || $roles['comember']); + $p['groupoptinapi'] = ($roles['cmadmin'] || $roles['coadmin'] || $roles['comember']); + $p['groupmember'] = ($roles['cmadmin'] || $roles['coadmin'] || $roles['comember']); + $p['groupmemberapi'] = ($roles['cmadmin'] || $roles['coadmin'] || $roles['comember']); + $p['getBaseConfig'] = ($roles['cmadmin'] || $roles['coadmin'] || $roles['comember']); + $p['groupSubscribers'] = ($roles['cmadmin'] || $roles['coadmin'] || $roles['comember']); + $p['addSubscriber'] = ($roles['cmadmin'] || $roles['coadmin'] || $roles['comember']); + $p['findSubscriber'] = ($roles['cmadmin'] || $roles['coadmin'] || $roles['comember']); + $p['usermanager'] = ($roles['cmadmin'] || $roles['coadmin'] || $roles['comember']); + $p['usermanagerapi'] = ($roles['cmadmin'] || $roles['coadmin'] || $roles['comember']); + $p['removeSubscriber'] = ($roles['cmadmin'] || $roles['coadmin'] || $roles['comember']); + + $p['groupCreate'] = ($roles['cmadmin'] || $roles['coadmin'] || $roles['comember']); + $p['joinGroup'] = ($roles['cmadmin'] || $roles['coadmin'] || $roles['comember']); + $p['leaveGroup'] = ($roles['cmadmin'] || $roles['coadmin'] || $roles['comember']); + $p['groupcreatetemplate'] = ($roles['cmadmin'] || $roles['coadmin'] || $roles['comember']); $this->set('permissions', $p); @@ -597,6 +605,14 @@ public function restResponse(int $status, $this->response->send(); } + /** + * @param null $userId + */ + private function setUserId($userId): void + { + $this->userId = $userId; + } + /** * UserManager vue route for rendering * @@ -607,6 +623,37 @@ public function userManager(): void $this->render('index'); } + /** + * Display all Groups for the user i manage + */ + public function userManagerApi(): void + { + //Set initial setting + $cfg = $this->CoGrouperLiteWidget->getConfig(); + + if (!isset($this->request->query['memberid'])) { + $this->restResponse(HttpStatusCodesEnum::HTTP_BAD_REQUEST, ErrorsEnum::Exception); + } + + $memberId = urldecode($this->request->query['memberid']); + + try { + $groupsimanage = $this->GrouperGroup->getManagedUsers($this->userId, + $memberId, + $cfg); + } catch (Exception $e) { + CakeLog::write('error', __METHOD__ . '::get Managed Users: ' . var_export($e->getMessage(), true)); + $this->restResponse(HttpStatusCodesEnum::HTTP_INTERNAL_SERVER_ERROR, ErrorsEnum::Exception); + + $this->set('groupsimanage', []); + return; + } + + $this->set(compact('groupsimanage')); + $this->set('_serialize', 'groupsimanage'); + + } + /** * Override the default sanity check performed in AppController * diff --git a/Lib/lang.php b/Lib/lang.php index 43afa4e..d5e490b 100644 --- a/Lib/lang.php +++ b/Lib/lang.php @@ -121,6 +121,7 @@ 'pl.grouperlite.action.view-members' => 'View members', 'pl.grouperlite.action.grouper' => 'Grouper', 'pl.grouperlite.action.members' => 'Members', + 'pl.grouperlite.action.memberships' => 'Memberships', 'pl.grouperlite.action.close' => 'Close', 'pl.grouperlite.action.clear' => 'Clear', 'pl.grouperlite.action.add-user' => 'Add', diff --git a/Model/GrouperGroup.php b/Model/GrouperGroup.php index 49172c6..e32c589 100644 --- a/Model/GrouperGroup.php +++ b/Model/GrouperGroup.php @@ -318,6 +318,55 @@ public function getOwnedGroups(string $userId, array $cfg): array return $this->removeDuplicates($resultsAdmin, $resultsUpdate); } + /** + * Return all Grouper Groups that + * - the User(me) has a role of owner/admin + * - the User(member User) is a member + * + * @param string $userId + * @param string $memberId + * @param array $cfg + * + * @return array + * @throws GrouperLiteWidgetException + * @since COmanage Registry v4.4.0 + */ + public function getManagedUsers(string $managerId, string $memberId, array $cfg): array { + if(empty($managerId) || empty($memberId)) { + return false; + } + + $this->initApi($cfg); + + try { + $resultsManagerAdmin = $this->grouperAPI->getUserMemberships($managerId, $managerId, GrouperGroupTypeEnum::ADMIN); + $resultsManagerUpdate = $this->grouperAPI->getUserMemberships($managerId, $managerId, GrouperGroupTypeEnum::UPDATE); + } catch (Exception $e) { + CakeLog::write('error', __METHOD__ . ': An error occurred'); + throw $e; + } + + $managerGroupSet = $this->removeDuplicates($resultsManagerAdmin, $resultsManagerUpdate); + + try { + // Groups the user is a member of + $membersGroup = $this->grouperAPI->getUserGroups($memberId, $memberId); + } catch (Exception $e) { + CakeLog::write('error', __METHOD__ . ': An error occurred'); + throw $e; + } + + // Extract the names of the Groups the member-user is a member of + $memberGroupNames = Hash::extract($membersGroup, '{n}.name'); + // Return the groups the user can join and is not a member of + return array_values( // Restart indexing from 0(zero) on the final array + array_filter( // Return the groups the member-user is a member + $managerGroupSet, + static fn($value) => in_array($value['name'], $memberGroupNames) + ) + ); + } + /** * Potential use was for creating adhoc group by a user, not associated to WG. * diff --git a/View/GrouperGroups/index.ctp b/View/GrouperGroups/index.ctp index f22b2e9..138e8a3 100644 --- a/View/GrouperGroups/index.ctp +++ b/View/GrouperGroups/index.ctp @@ -93,6 +93,7 @@ removeSubscriberSuccess: "", getSubscriberError: "", peoplePickerPlaceHolder: "", + memberships: "", noaccess: "", empty: "", join: "", @@ -129,6 +130,7 @@ add: "webroot ?>grouper_lite_widget/grouper_groups/addSubscriber/co:/glid:", group: "webroot ?>grouper_lite_widget/grouper_groups/groupSubscribers/co:/glid:", memberships: "webroot ?>grouper_lite_widget/grouper_groups/groupmemberapi/co:/glid:", + managing: "webroot ?>grouper_lite_widget/grouper_groups/usermanagerapi/co:/glid:", optin: "webroot ?>grouper_lite_widget/grouper_groups/groupoptinapi/co:/glid:", owner: "webroot ?>grouper_lite_widget/grouper_groups/groupownerapi/co:/glid:", }, diff --git a/webroot/js/autocomplete.js b/webroot/js/autocomplete.js index 8fb8a54..c86d92f 100644 --- a/webroot/js/autocomplete.js +++ b/webroot/js/autocomplete.js @@ -2,10 +2,6 @@ export default { props: { - group: { - type: String, - default: "" - }, action: { type: String, default: "addUser" @@ -13,6 +9,10 @@ export default { icon: { type: String, default: 'add' + }, + activeBtn: { + type: Boolean, + default: true } }, inject: ['txt', 'api'], @@ -86,7 +86,7 @@ export default { class=" btn btn-grouper btn-primary px-4 border-0" type="button" @click="performAction()" - :disabled="true"> + :disabled="activeBtn"> {{ btnTxt }} diff --git a/webroot/js/members.js b/webroot/js/members.js index 4900d03..c031bd1 100644 --- a/webroot/js/members.js +++ b/webroot/js/members.js @@ -163,7 +163,7 @@ export default {
- +
diff --git a/webroot/js/page/UserManager.js b/webroot/js/page/UserManager.js index 2c14fb8..cbc874d 100644 --- a/webroot/js/page/UserManager.js +++ b/webroot/js/page/UserManager.js @@ -13,32 +13,31 @@ export default { Pagination, GroupsTable, Members, - Autocomplete + Autocomplete, + Loader + }, + data() { + return { + loading: false, + result: [], + } }, inject: ['api', 'txt'], methods: { - showSubscribers(group) { - this.$refs.members.show(group); - }, - async findUserMemberships(user) { + async findManagedUsers(user) { const { identifier: id, label } = user; this.loading = true; - const { displayExtension, name } = this.group; - const formData = new FormData(); - formData.append("userId", id); - formData.append("group", name); - const resp = await fetch(`${this.api.add}?group=${name}&userId=${id}`, { - method: "POST", + const resp = await fetch(`${this.api.managing}?memberid=${id}`, { headers: { "Accept": "application/json", }, - body: formData + method: "GET" }); if (resp.ok) { - await this.loadGroupSubscribers(this.group); - generateFlash(`${label} ${this.txt.findUserMembershipsSuccess} ${(displayExtension)}`, 'success'); + this.result = await resp.json(); + console.log('result', this.result) + generateFlash(`${id} has ${this.result.length} ${this.txt.memberships}`, 'success'); } else { - generateFlash(`${this.txt.findUserMembershipsError}`, 'error'); let errorResponse = await resp.json(); generateFlash(`${errorResponse.message}`, 'error'); } @@ -47,24 +46,11 @@ export default { } }, template: /*html*/` - - - - - - - - - - - - - - - - + ` } \ No newline at end of file From cf2042555c0ad186a69eded488f132414e874c74 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Fri, 8 Mar 2024 10:46:19 +0200 Subject: [PATCH 29/60] autocomplete people picker improvements --- webroot/js/autocomplete.js | 9 +++++++-- webroot/js/members.js | 2 +- webroot/js/page/UserManager.js | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/webroot/js/autocomplete.js b/webroot/js/autocomplete.js index c86d92f..ae8235a 100644 --- a/webroot/js/autocomplete.js +++ b/webroot/js/autocomplete.js @@ -25,7 +25,7 @@ export default { }, methods: { performAction() { - this.$emit(this.action, this.item); + this.$emit('callback', this.item); }, toKebabCase(str) { return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); @@ -34,6 +34,11 @@ export default { computed: { btnTxt() { return eval(`this.txt.${this.action}`) ?? eval(`this.txt.${this.icon}`) + }, + isBtnDisabled() { + // The minimum length that i start search is 3. So we only enable the button when + // the input text value has at least three characters + return this.activeBtn || (this.search.length < 3) } }, mounted(el) { @@ -86,7 +91,7 @@ export default { class=" btn btn-grouper btn-primary px-4 border-0" type="button" @click="performAction()" - :disabled="activeBtn"> + :disabled="isBtnDisabled"> {{ btnTxt }} diff --git a/webroot/js/members.js b/webroot/js/members.js index c031bd1..7c900ac 100644 --- a/webroot/js/members.js +++ b/webroot/js/members.js @@ -163,7 +163,7 @@ export default {
- +
diff --git a/webroot/js/page/UserManager.js b/webroot/js/page/UserManager.js index cbc874d..0677a2c 100644 --- a/webroot/js/page/UserManager.js +++ b/webroot/js/page/UserManager.js @@ -46,7 +46,7 @@ export default { } }, template: /*html*/` - Date: Fri, 8 Mar 2024 21:11:05 +0200 Subject: [PATCH 30/60] autocomplete improvements.Other improvements and minor fixes. --- Controller/GrouperGroupsController.php | 4 +-- Model/GrouperGroup.php | 12 +++++-- View/GrouperGroups/base.ctp | 1 + webroot/js/autocomplete.grouperplugin.js | 21 +++++++++++ webroot/js/autocomplete.js | 46 +++++++++++++++--------- webroot/js/members.js | 2 +- webroot/js/page/GroupMember.js | 6 +--- webroot/js/page/UserManager.js | 14 ++++++-- 8 files changed, 77 insertions(+), 29 deletions(-) create mode 100644 webroot/js/autocomplete.grouperplugin.js diff --git a/Controller/GrouperGroupsController.php b/Controller/GrouperGroupsController.php index 1657acd..b9749a0 100644 --- a/Controller/GrouperGroupsController.php +++ b/Controller/GrouperGroupsController.php @@ -67,8 +67,8 @@ public function addSubscriber(): void $this->layout = null; $this->autoRender = false; - $groupName = urldecode($this->request->query['group']); - $addUserId = urldecode($this->request->query['userId']); + $groupName = $this->request->data['group']; + $addUserId = $this->request->data['userId']; // Need to see if coming from AdHoc or from a WG (Working Group) $groupNameFormatted = strpos($groupName, ':') === false ? 'ref:incommon-collab:' . $groupName . ':users' diff --git a/Model/GrouperGroup.php b/Model/GrouperGroup.php index e32c589..e90fb0b 100644 --- a/Model/GrouperGroup.php +++ b/Model/GrouperGroup.php @@ -217,8 +217,10 @@ public function findForPicker(int $coId, string $mode, ?string $term): array $idArr = $p['Identifier']; $emailArr = $p['EmailAddress']; $email = ''; + $email_short = ''; $emailLabel = ''; $id = ''; + $id_short = ''; $idLabel = ''; // Iterate over the email array @@ -228,6 +230,7 @@ public function findForPicker(int $coId, string $mode, ?string $term): array foreach($emailArr as $e) { if($e['type'] == $pickerEmailType) { $email = $e['mail']; + $email_short = mb_strimwidth($e['mail'], 0, 30, '...'); break; } } @@ -243,7 +246,8 @@ public function findForPicker(int $coId, string $mode, ?string $term): array } foreach($idArr as $i) { if($i['type'] == $pickerIdentifierType) { - $id = mb_strimwidth($i['identifier'], 0, 30, '...'); + $id_short = mb_strimwidth($i['identifier'], 0, 30, '...'); + $id = $i['identifier']; break; } } @@ -255,9 +259,13 @@ public function findForPicker(int $coId, string $mode, ?string $term): array 'value' => $p['CoPerson']['id'], 'label' => $label, 'email' => $email, + 'emailShort' => $email_short, 'emailLabel' => $emailLabel, + 'emailType' => $pickerEmailType, 'identifier' => $id, - 'identifierLabel' => $idLabel + 'identifierShort' => $id_short, + 'identifierLabel' => $idLabel, + 'identifierType' => $pickerIdentifierType ); } } diff --git a/View/GrouperGroups/base.ctp b/View/GrouperGroups/base.ctp index 8301abc..ef2c37d 100644 --- a/View/GrouperGroups/base.ctp +++ b/View/GrouperGroups/base.ctp @@ -16,6 +16,7 @@ echo $this->Html->meta( array('inline' => false) ); +print $this->Html->script('GrouperLiteWidget.autocomplete.grouperplugin') . PHP_EOL; print $this->element('GrouperLiteWidget.base-styles'); print $this->Html->css('GrouperLiteWidget.co-grouper-plugin') . PHP_EOL; diff --git a/webroot/js/autocomplete.grouperplugin.js b/webroot/js/autocomplete.grouperplugin.js new file mode 100644 index 0000000..0b37dc9 --- /dev/null +++ b/webroot/js/autocomplete.grouperplugin.js @@ -0,0 +1,21 @@ +$.widget( "ui.autocomplete", $.ui.autocomplete, { + _renderMenu: function( ul, items ) { + var that = this; + $.each( items, function( index, item ) { + that._renderItemData( ul, item ); + }); + }, + _renderItem: function( ul, item ) { + let itemMarkup = '
'; + itemMarkup += '
' + item.label + '
'; + if(item?.emailShort != '') { + itemMarkup += ''; + } + if(item?.identifierShort != '') { + itemMarkup += '
' + item.identifierLabel + '' + item.identifierShort + '
'; + } + itemMarkup += '
'; + + return $("
  • ").append(itemMarkup).appendTo(ul); + } +}); \ No newline at end of file diff --git a/webroot/js/autocomplete.js b/webroot/js/autocomplete.js index ae8235a..52d81c7 100644 --- a/webroot/js/autocomplete.js +++ b/webroot/js/autocomplete.js @@ -21,6 +21,9 @@ export default { search: '', val: '', item: null, + limit: 15, + minLength: 3, + url: '' }; }, methods: { @@ -43,27 +46,36 @@ export default { }, mounted(el) { const input = $(this.$el).find(`#${this.toKebabCase(this.action)}-input`); + this.url = `${this.api.find}?co=${this.api.co}&mode=${this.api.mode}&page=${this.page}&limit=${this.limit}` input.autocomplete({ - source: `${this.api.find}?co=${this.api.co}&mode=${this.api.mode}`, - minLength: 3, - maxShowItems: 15, - focus: ( event, ui ) => { - $("#grouper-search-container .co-loading-mini").hide(); - return false; - }, - open: function (event, ui) { - $("#grouper-search-container .co-loading-mini").hide(); + source: ( request, response ) => { + $("#grouper-search-container .co-loading-mini").show(); + $.ajax({ + url: this.url, + type: 'GET', + dataType: "json", + data: { + // XXX Change the term key to any other query key that fits your needs. + term: request.term + }, + success: function (data) { + $("#grouper-search-container .co-loading-mini").hide(); + // If i have more data from before append at the end + response( data ); + }, + error: function(data) { + $("#grouper-search-container .co-loading-mini").hide(); + console.log('Autocomplete ajax error:', data) + generateFlash('Find action failed', 'error'); + } + }); }, + delay: 1000, + minLength: this.minLength, + maxShowItems: this.limit, create: (event, ui) => { - // debugger $(`#${this.toKebabCase(this.action)}-input`).focus(); }, - close: function (event, ui) { - $("#grouper-search-container .co-loading-mini").hide(); - }, - search: function (event, ui) { - $("#grouper-search-container .co-loading-mini").show(); - }, // XXX We need access to the parent data object. // As a result we have to use arrow function syntax (ES6) select: (event, ui) => { @@ -73,7 +85,7 @@ export default { $(`#${this.toKebabCase(this.action)}-btn`).prop('disabled', false).focus(); return false; }, - }).autocomplete( "instance" )._renderItem = formatCoPersonAutoselectItem; + }) }, template: /*html*/`
    diff --git a/webroot/js/members.js b/webroot/js/members.js index 7c900ac..9c3edac 100644 --- a/webroot/js/members.js +++ b/webroot/js/members.js @@ -116,7 +116,7 @@ export default { const formData = new FormData(); formData.append("userId", id); formData.append("group", name); - const resp = await fetch(`${this.api.add}?group=${name}&userId=${id}`, { + const resp = await fetch(`${this.api.add}`, { method: "POST", headers: { "Accept": "application/json", diff --git a/webroot/js/page/GroupMember.js b/webroot/js/page/GroupMember.js index a6f781b..09da695 100644 --- a/webroot/js/page/GroupMember.js +++ b/webroot/js/page/GroupMember.js @@ -23,6 +23,7 @@ export default { }, methods: { showSubscribers(group) { + // Create and show the modal this.$refs.members.show(group); }, }, @@ -31,11 +32,6 @@ export default { setQueryParam('view', newValue); } }, - computed: { - routePath() { - return `/groupmember/co:${this.api.co}/glid:${this.api.glid}`; - } - }, mounted() { let view = hasQueryParam('view') ? getQueryParam('view') : 'adhoc'; if (view !== 'working' && view !== 'adhoc') { diff --git a/webroot/js/page/UserManager.js b/webroot/js/page/UserManager.js index 0677a2c..7d9cd80 100644 --- a/webroot/js/page/UserManager.js +++ b/webroot/js/page/UserManager.js @@ -1,4 +1,3 @@ -import Groups from '../groups.js'; import PageCount from '../pagecount.js'; import Pagination from '../pagination.js'; import GroupsTable from '../groups-table.js'; @@ -8,7 +7,6 @@ import Loader from '../loader.js'; export default { components: { - Groups, PageCount, Pagination, GroupsTable, @@ -24,6 +22,9 @@ export default { }, inject: ['api', 'txt'], methods: { + dummy() { + console.log('hi from dummy') + }, async findManagedUsers(user) { const { identifier: id, label } = user; this.loading = true; @@ -52,5 +53,14 @@ export default { :activeBtn="loading" /> + + + ` } \ No newline at end of file From 6af044c35185a212485486114111717b87a151ad Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Wed, 13 Mar 2024 20:34:17 +0200 Subject: [PATCH 31/60] pass userManagerId to getUserGroups API Call --- Model/GrouperGroup.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Model/GrouperGroup.php b/Model/GrouperGroup.php index e90fb0b..1468b07 100644 --- a/Model/GrouperGroup.php +++ b/Model/GrouperGroup.php @@ -331,7 +331,7 @@ public function getOwnedGroups(string $userId, array $cfg): array * - the User(me) has a role of owner/admin * - the User(member User) is a member * - * @param string $userId + * @param string $managerId * @param string $memberId * @param array $cfg * @@ -358,7 +358,7 @@ public function getManagedUsers(string $managerId, string $memberId, array $cfg) try { // Groups the user is a member of - $membersGroup = $this->grouperAPI->getUserGroups($memberId, $memberId); + $membersGroup = $this->grouperAPI->getUserGroups($managerId, $memberId); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; From 060b668765d5bb00eba52b977a6ba34db774c734 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Thu, 14 Mar 2024 12:56:21 +0200 Subject: [PATCH 32/60] Present data in table.Add and test action. --- webroot/css/co-grouper-plugin.css | 8 ++++ webroot/js/groups-table.js | 29 +++++++++++--- webroot/js/page/UserManager.js | 64 ++++++++++++++++++++++++------- webroot/js/pagecount.js | 6 ++- 4 files changed, 87 insertions(+), 20 deletions(-) diff --git a/webroot/css/co-grouper-plugin.css b/webroot/css/co-grouper-plugin.css index 4736499..9b1cebf 100644 --- a/webroot/css/co-grouper-plugin.css +++ b/webroot/css/co-grouper-plugin.css @@ -327,4 +327,12 @@ a.list-group-item-action:hover .fa { right: 38px; bottom: 10px; margin-right: -26px; +} + +.grouper_groups #co-loading span, +.grouper_groups #co-loading-redirect span, +.grouper_groups .co-loading-mini span { + animation: 1.2s linear infinite both loading; + background-color: var(--teal); + display: inline-block; } \ No newline at end of file diff --git a/webroot/js/groups-table.js b/webroot/js/groups-table.js index 49fe26a..89ba9fd 100644 --- a/webroot/js/groups-table.js +++ b/webroot/js/groups-table.js @@ -6,7 +6,10 @@ export default { Table ], data() {}, - computed: { + methods: { + groupStatus(status) { + return status === 'T' ? 'Enabled' : 'Disabled' + } }, created() {}, template: /*html*/` @@ -25,9 +28,10 @@ export default { — {{ group.description || txt.descrZeroState }} - {{ group.enabled && group.enabled === 'T' ? 'Enabled' : 'Disabled' }} + {{ groupStatus(group?.enabled) }} + + - + + + + {{ txt.grouper }}   diff --git a/webroot/js/page/UserManager.js b/webroot/js/page/UserManager.js index 7d9cd80..256f868 100644 --- a/webroot/js/page/UserManager.js +++ b/webroot/js/page/UserManager.js @@ -17,17 +17,51 @@ export default { data() { return { loading: false, - result: [], + results: [], + user: {}, + rowLoading: false, + searched: false } }, inject: ['api', 'txt'], methods: { - dummy() { - console.log('hi from dummy') + reset() { + this.searched = false + // If you are in the middle of a row action we do not want to clear + // the results because we are breaking the user experience + if(!this.rowLoading) { + this.results = [] + } + }, + async removeUser(group) { + const {name, displayExtension} = group; + const { identifier: id, label: username, email } = this.user; + // This will trigger the inline loader + group.loading = true; + this.rowLoading = true; + const resp = await fetch(`${this.api.remove}?group=${(name)}&userId=${id}`, { + method: "DELETE", + headers: { + "Accept": "application/json" + } + }); + if (resp.ok) { + this.subscribers = []; + await this.findGroupsForManagedUser(this.user); + generateFlash(`${username ?? email} ${this.txt.removeSubscriberSuccess} ${(displayExtension)}`, 'success'); + } else { + this.disabled = [ ...this.disabled, id ]; + generateFlash(this.txt.removeSubscriberError, 'error'); + let errorResponse = await resp.json(); + generateFlash(`${errorResponse.message}`, 'error'); + } }, - async findManagedUsers(user) { - const { identifier: id, label } = user; - this.loading = true; + async findGroupsForManagedUser(user) { + this.user = user + // Reset + this.reset() + const { identifier: id } = user; + this.loading = !this.rowLoading && true; const resp = await fetch(`${this.api.managing}?memberid=${id}`, { headers: { "Accept": "application/json", @@ -35,31 +69,35 @@ export default { method: "GET" }); if (resp.ok) { - this.result = await resp.json(); - console.log('result', this.result) - generateFlash(`${id} has ${this.result.length} ${this.txt.memberships}`, 'success'); + this.results = await resp.json(); + generateFlash(`${id} has ${this.results.length} ${this.txt.memberships}`, 'success'); } else { let errorResponse = await resp.json(); generateFlash(`${errorResponse.message}`, 'error'); } + this.searched = true; this.loading = false; } }, template: /*html*/` - ` diff --git a/webroot/js/pagecount.js b/webroot/js/pagecount.js index 1b41421..5f9c83e 100644 --- a/webroot/js/pagecount.js +++ b/webroot/js/pagecount.js @@ -11,12 +11,16 @@ export default { total: { type: Number, default: 100 + }, + searched: { + type: Boolean, + default: true } }, inject: ['txt'], template: /*html*/`
    -
    +
    From 29196ea7786407407bd461a4f2a58bc073578645 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Mon, 18 Mar 2024 20:50:23 +0200 Subject: [PATCH 33/60] Initiate searching for groups as soon as i select the user from the dropdown.Skip button click. --- webroot/js/autocomplete.js | 23 ++++++++++++++++++++--- webroot/js/page/UserManager.js | 9 ++++++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/webroot/js/autocomplete.js b/webroot/js/autocomplete.js index 52d81c7..368fec1 100644 --- a/webroot/js/autocomplete.js +++ b/webroot/js/autocomplete.js @@ -13,6 +13,14 @@ export default { activeBtn: { type: Boolean, default: true + }, + renderBtn: { + type: Boolean, + default: true + }, + preInfo: { + type: String, + default: '' } }, inject: ['txt', 'api'], @@ -82,13 +90,22 @@ export default { this.val = ui.item.identifier; this.item = ui.item; this.search = `${ui.item.label} (${ui.item.value})`; - $(`#${this.toKebabCase(this.action)}-btn`).prop('disabled', false).focus(); - return false; + if(this.renderBtn) { + $(`#${this.toKebabCase(this.action)}-btn`).prop('disabled', false).focus(); + } else { + // Since we are not rendering any button we will trigger the search + this.performAction() + } }, }) }, template: /*html*/`
    +
    + + + +
    -
    +
    +
    - John Doe
    diff --git a/webroot/css/co-grouper-plugin.css b/webroot/css/co-grouper-plugin.css index 2175744..6e8f27e 100644 --- a/webroot/css/co-grouper-plugin.css +++ b/webroot/css/co-grouper-plugin.css @@ -14,6 +14,10 @@ border-radius: .2rem; } +.btn-fit { + height: fit-content; +} + a { color: var(--primary); } diff --git a/webroot/js/autocomplete.js b/webroot/js/autocomplete.js index a60e81d..da566ce 100644 --- a/webroot/js/autocomplete.js +++ b/webroot/js/autocomplete.js @@ -120,7 +120,7 @@ export default { type="button" @click="performAction()" :disabled="isBtnDisabled"> - + {{ btnTxt }}
    From 23ca0d482cd4f146b98ae236886ad1616caf266e Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Tue, 2 Apr 2024 19:26:34 +0300 Subject: [PATCH 41/60] render action sidebar if the user belongs to the appropriate group --- Controller/GrouperGroupsController.php | 13 +- .../GrouperLiteActAsPeopleController.php | 135 ++++++++++++++++++ Model/GrouperGroup.php | 25 ++++ Model/GrouperLiteActAsPerson.php | 18 ++- View/GrouperGroups/index.ctp | 6 +- 5 files changed, 193 insertions(+), 4 deletions(-) create mode 100644 Controller/GrouperLiteActAsPeopleController.php diff --git a/Controller/GrouperGroupsController.php b/Controller/GrouperGroupsController.php index a9790da..edf0bcf 100644 --- a/Controller/GrouperGroupsController.php +++ b/Controller/GrouperGroupsController.php @@ -111,7 +111,7 @@ public function beforeFilter() HttpStatusCodesEnum::HTTP_BAD_REQUEST); } $this->response->disableCache(); - $this->RequestHandler->addInputType('json', array('json_decode', true)); + $this->RequestHandler->addInputType('json', ['json_decode', true]); $this->Security->unlockedActions = [ 'removeSubscriber', @@ -125,7 +125,7 @@ public function beforeFilter() // Get the config $args = array(); - $args['conditions']['CoGrouperLiteWidget.id'] = $this->request->params["named"]["glid"]; + $args['conditions']['CoGrouperLiteWidget.id'] = $this->request->params['named']['glid']; $args['contain'] = false; $cfg = $this->CoGrouperLiteWidget->find('first', $args); // Set the config so that everybody can access it @@ -436,6 +436,14 @@ public function isAuthorized(): array|bool $this->setUserId($identifiers['Identifier']['identifier']); } + // Find if the user belongs to Group + $eligibleGroup = $cfg['CoGrouperLiteWidget']['act_as_grp_name']; + $isActAsEligibilityGroupmember = false; + + if(!empty($eligibleGroup)) { + $isActAsEligibilityGroupmember = $this->GrouperGroup->isGroupMember($this->getUserId(), $eligibleGroup, $cfg); + } + // Determine what operations this user can perform // Construct the permission set for this user, which will also be passed to the view. $p = []; @@ -459,6 +467,7 @@ public function isAuthorized(): array|bool $p['joinGroup'] = ($roles['cmadmin'] || $roles['coadmin'] || $roles['comember']); $p['leaveGroup'] = ($roles['cmadmin'] || $roles['coadmin'] || $roles['comember']); $p['groupcreatetemplate'] = ($roles['cmadmin'] || $roles['coadmin'] || $roles['comember']); + $p['actAsAction'] = $isActAsEligibilityGroupmember; $this->set('permissions', $p); diff --git a/Controller/GrouperLiteActAsPeopleController.php b/Controller/GrouperLiteActAsPeopleController.php new file mode 100644 index 0000000..d69da89 --- /dev/null +++ b/Controller/GrouperLiteActAsPeopleController.php @@ -0,0 +1,135 @@ + [ + 'validatePost' => false, + 'csrfUseOnce' => false + ] + ]; + + + public $name = 'GrouperLiteActAsPeople'; + + /** + * Overrides parent beforeFilter to verify that Session contains the correct API settings. + * + * @return void + */ + public function beforeFilter() + { + parent::beforeFilter(); + + if(empty($this->request->params['named']['glid'])) { + throw new InvalidArgumentException(_txt('er.grouperlite.glid'), + HttpStatusCodesEnum::HTTP_BAD_REQUEST); + } + $this->response->disableCache(); + $this->RequestHandler->addInputType('json', ['json_decode', true]); + + $this->Security->unlockedActions = [ + 'add', + 'edit', + 'delete' + ]; + + // Get the config + $args = array(); + $args['conditions']['CoGrouperLiteWidget.id'] = $this->request->params['named']['glid']; + $args['contain'] = false; + $cfg = $this->CoGrouperLiteWidget->find('first', $args); + // Set the config so that everybody can access it + $this->CoGrouperLiteWidget->setConfig($cfg); + } + + /** + * NOTE: All permissions will be done on the Grouper side. All Authenticated users will be able to + * use this plugin for self-admin of groups. + * + * Authorization for this Controller, called by Auth component + * - precondition: Session.Auth holds data used for authz decisions + * - postcondition: $permissions set with calculated permissions + * + * @return array|bool Permissions + * @since COmanage Registry v4.4.0 + */ + public function isAuthorized(): array|bool + { + $roles = $this->Role->calculateCMRoles(); + $cfg = $this->CoGrouperLiteWidget->getConfig(); + // Find the identifier + $args = array(); + $args['conditions']['Identifier.type'] = $cfg['CoGrouperLiteWidget']['identifier_type']; + $args['conditions']['Identifier.status'] = SuspendableStatusEnum::Active; + $args['conditions']['Identifier.co_person_id'] = $roles['copersonid']; + $args['contain'] = false; + + $identifiers = $this->Identifier->find('first', $args); + if(!empty($identifiers) + && is_array($identifiers) + && isset($identifiers['Identifier']['identifier']) + ) { + $this->setUserId($identifiers['Identifier']['identifier']); + } + + // Find if the user belongs to Group + $eligibleGroup = $cfg['CoGrouperLiteWidget']['act_as_grp_name']; + $isActAsEligibilityGroupmember = false; + + if(!empty($eligibleGroup)) { + $isActAsEligibilityGroupmember = $this->GrouperGroup->isGroupMember($this->getUserId(), $eligibleGroup, $cfg); + } + + // Determine what operations this user can perform + // Construct the permission set for this user, which will also be passed to the view. + $p = []; + + $p['add'] = $isActAsEligibilityGroupmember; + $p['delete'] = $isActAsEligibilityGroupmember; + $p['edit'] = $isActAsEligibilityGroupmember; + $p['update'] = $isActAsEligibilityGroupmember; + + $this->set('permissions', $p); + + return ($p[$this->action]); + } + + /** + * @return null + */ + public function getUserId() + { + return $this->userId; + } + + + /** + * @param null $userId + */ + private function setUserId($userId): void + { + $this->userId = $userId; + } +} \ No newline at end of file diff --git a/Model/GrouperGroup.php b/Model/GrouperGroup.php index 1468b07..6f14364 100644 --- a/Model/GrouperGroup.php +++ b/Model/GrouperGroup.php @@ -528,6 +528,31 @@ public function optinGroups(string $userId, array $cfg): array ); } + /** + * Determine if a User can use the Grouper Template to create a Working Group. + * + * @param string $userId User ID + * @param string $groupName Group Name + * @param array $cfg + * + * @return bool T for True and F for False + * @throws GrouperLiteWidgetException + * @since COmanage Registry v4.4.0 + */ + public function isGroupMember(string $userId, string $groupName, array $cfg): bool + { + $this->initApi($cfg); + + try { + $isMember = $this->grouperAPI->isMemberOfGroup($groupName, $userId); + } catch (Exception $e) { + CakeLog::write('error', __METHOD__ . ': An error occurred'); + throw $e; + } + + return (bool)$isMember; + } + /** * Determine if User can use the Grouper Template to create a Working Group. * diff --git a/Model/GrouperLiteActAsPerson.php b/Model/GrouperLiteActAsPerson.php index 7d2eb9c..4f833e4 100644 --- a/Model/GrouperLiteActAsPerson.php +++ b/Model/GrouperLiteActAsPerson.php @@ -29,6 +29,21 @@ class GrouperLiteActAsPerson extends AppModel { public $name = 'GrouperLiteActAsPerson'; + public $cmPluginHasMany = [ + "CoGrouperLiteWidget" => ["GrouperLiteActAsPerson"], + 'CoPerson' => [ + 'GrouperLiteActAsPerson' => [ + 'className' => 'GrouperLiteActAsPerson', + 'foreignKey' => 'actor_co_person_id' + ] + ], + 'CoPerson' => [ + 'GrouperLiteActAsPerson' => [ + 'className' => 'GrouperLiteActAsPerson', + 'foreignKey' => 'act_as_co_person_id' + ] + ] + ]; // Association rules from this model to other models public $belongsTo = [ @@ -40,7 +55,8 @@ class GrouperLiteActAsPerson extends AppModel 'ActorCoPerson' => [ 'className' => 'CoPerson', 'foreignKey' => 'actor_co_person_id' - ] + ], + 'CoGrouperLiteWidget' ]; // Validation rules for table elements diff --git a/View/GrouperGroups/index.ctp b/View/GrouperGroups/index.ctp index bc80b10..bf00aba 100644 --- a/View/GrouperGroups/index.ctp +++ b/View/GrouperGroups/index.ctp @@ -124,6 +124,7 @@ $suffix = Configure::read('debug') > 0 ? '?time=' . time() : ''; JSON_THROW_ON_ERROR) ?>, }, api: { + permissions: , co: , glid: , mode: "", @@ -151,10 +152,12 @@ $suffix = Configure::read('debug') > 0 ? '?time=' . time() : ''; -
    +
    + \ No newline at end of file From 3d6efd2d7757b6702431f0129c2ae8c38786496a Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Tue, 2 Apr 2024 21:43:26 +0300 Subject: [PATCH 42/60] implemented ActAsPerson add action --- Config/Schema/schema.xml | 4 +- ...ntroller.php => ActAsPeopleController.php} | 76 +++++++++++++++++-- Controller/GrouperGroupsController.php | 3 + ...perLiteActAsPerson.php => ActAsPerson.php} | 30 ++++---- Model/GrouperGroup.php | 4 +- Model/GrouperLiteWidget.php | 2 +- View/Elements/actAsPeopleAutocomplete.ctp | 52 +++++++++++-- 7 files changed, 136 insertions(+), 35 deletions(-) rename Controller/{GrouperLiteActAsPeopleController.php => ActAsPeopleController.php} (59%) rename Model/{GrouperLiteActAsPerson.php => ActAsPerson.php} (74%) diff --git a/Config/Schema/schema.xml b/Config/Schema/schema.xml index e978574..9b65be3 100644 --- a/Config/Schema/schema.xml +++ b/Config/Schema/schema.xml @@ -52,7 +52,7 @@ - +
    @@ -63,7 +63,7 @@ REFERENCES cm_co_people(id) - + REFERENCES cm_co_people(id) diff --git a/Controller/GrouperLiteActAsPeopleController.php b/Controller/ActAsPeopleController.php similarity index 59% rename from Controller/GrouperLiteActAsPeopleController.php rename to Controller/ActAsPeopleController.php index d69da89..eb872ff 100644 --- a/Controller/GrouperLiteActAsPeopleController.php +++ b/Controller/ActAsPeopleController.php @@ -2,19 +2,23 @@ App::uses('Validator', 'Vendor/cakephp/Validation'); App::uses('CoGrouperLite', 'GrouperLiteWidget.Model/'); +App::uses('ActAsPerson', 'GrouperLiteWidget.Model/'); App::uses('GrouperGroup', 'GrouperLiteWidget.Model/'); + App::uses('Identifier', 'Model'); -class GrouperLiteActAsPeopleController extends StandardController +class ActAsPeopleController extends GrouperLiteWidgetAppController { public $helpers = ['Html', 'Form', 'Flash']; // Dynamic properties are deprecated, so we will define the property here private $userId; + public $requires_person = true; + public $uses = [ - 'GrouperLiteWidget.GrouperLiteActAsPerson', + 'GrouperLiteWidget.ActAsPerson', 'GrouperLiteWidget.CoGrouperLiteWidget', 'GrouperLiteWidget.GrouperGroup', 'Identifier', @@ -31,7 +35,7 @@ class GrouperLiteActAsPeopleController extends StandardController ]; - public $name = 'GrouperLiteActAsPeople'; + public $name = 'ActAsPeople'; /** * Overrides parent beforeFilter to verify that Session contains the correct API settings. @@ -64,6 +68,61 @@ public function beforeFilter() $this->CoGrouperLiteWidget->setConfig($cfg); } + /** + * Add an ActAs user + * + * @since COmanage Registry v4.4.0 + */ + + public function add(): void + { + $this->layout = null; + $this->autoRender = false; + + if(!$this->request->is('restful') + && !$this->request->is('ajax')) { + throw new RuntimeException('HTTP Method Not Allowed', HttpStatusCodesEnum::HTTP_METHOD_NOT_ALLOWED); + } + + $data = $this->request->data; + + try { + $ret = $this->ActAsPerson->saveAll($data); + } + catch(Exception $e) { + $err = filter_var($e->getMessage(),FILTER_SANITIZE_SPECIAL_CHARS); + } + + if($ret) { + // Reread the data so we account for any normalizations + $args = []; + $args['conditions']['ActAsPerson.id'] = $this->ActAsPerson->id; + $args['contain'] = false; + $data = $this->ActAsPerson->find('first', $args); + $this->Api->restResultHeader(201, "Added"); + $this->set('ActAsPerson', $this->ActAsPerson->id); + $this->response->body(json_encode($data['ActAsPerson'], JSON_THROW_ON_ERROR)); + } else { + $fs = $this->ActAsPerson->invalidFields(); + + if(!empty($fs)) { + $this->Api->restResultHeader(400, 'Invalid Fields'); + $this->set('invalid_fields', $fs); + } elseif ($e + && isset(_txt('en.http.status.codes')[$e->getCode()]) ) { + $this->Api->restResultHeader($e->getCode(), _txt('en.http.status.codes')[$e->getCode()]); + if(!empty($e->getMessage())) { + $this->set('vv_error', $e->getMessage()); + } + } else { + $this->Api->restResultHeader(500, "Other Error"); + } + } + + $this->response->send(); + } + + /** * NOTE: All permissions will be done on the Grouper side. All Authenticated users will be able to * use this plugin for self-admin of groups. @@ -78,12 +137,14 @@ public function beforeFilter() public function isAuthorized(): array|bool { $roles = $this->Role->calculateCMRoles(); + $pids = $this->parsePersonID($this->request->data); + $cfg = $this->CoGrouperLiteWidget->getConfig(); // Find the identifier $args = array(); $args['conditions']['Identifier.type'] = $cfg['CoGrouperLiteWidget']['identifier_type']; $args['conditions']['Identifier.status'] = SuspendableStatusEnum::Active; - $args['conditions']['Identifier.co_person_id'] = $roles['copersonid']; + $args['conditions']['Identifier.co_person_id'] = !empty($roles['copersonid']) ? $roles['copersonid'] : $pids['copersonid']; $args['contain'] = false; $identifiers = $this->Identifier->find('first', $args); @@ -99,7 +160,8 @@ public function isAuthorized(): array|bool $isActAsEligibilityGroupmember = false; if(!empty($eligibleGroup)) { - $isActAsEligibilityGroupmember = $this->GrouperGroup->isGroupMember($this->getUserId(), $eligibleGroup, $cfg); + $isActAsEligibilityGroupmember = $this->GrouperGroup + ->isGroupMember($this->getUserId(), $eligibleGroup, $cfg); } // Determine what operations this user can perform @@ -109,7 +171,9 @@ public function isAuthorized(): array|bool $p['add'] = $isActAsEligibilityGroupmember; $p['delete'] = $isActAsEligibilityGroupmember; $p['edit'] = $isActAsEligibilityGroupmember; - $p['update'] = $isActAsEligibilityGroupmember; + $p['index'] = $isActAsEligibilityGroupmember; + $p['view'] = $isActAsEligibilityGroupmember; + $p['patch'] = $isActAsEligibilityGroupmember; $this->set('permissions', $p); diff --git a/Controller/GrouperGroupsController.php b/Controller/GrouperGroupsController.php index edf0bcf..354c425 100644 --- a/Controller/GrouperGroupsController.php +++ b/Controller/GrouperGroupsController.php @@ -149,6 +149,9 @@ public function beforeRender() { // $this->set('vv_is_template_user', $this->GrouperGroup->isTemplateUser($this->userId ?? '', $cfg) ); // $this->set('vv_is_grouper_visible', $this->GrouperGroup->isGrouperVisible($this->userId ?? '', $cfg)); $this->set('vv_coid', $this->cur_co['Co']['id']); + + $roles = $this->Role->calculateCMRoles(); + $this->set('vv_copersonid', $roles['copersonid'] ?? null); } /** diff --git a/Model/GrouperLiteActAsPerson.php b/Model/ActAsPerson.php similarity index 74% rename from Model/GrouperLiteActAsPerson.php rename to Model/ActAsPerson.php index 4f833e4..15d6299 100644 --- a/Model/GrouperLiteActAsPerson.php +++ b/Model/ActAsPerson.php @@ -1,6 +1,6 @@ ["GrouperLiteActAsPerson"], - 'CoPerson' => [ - 'GrouperLiteActAsPerson' => [ - 'className' => 'GrouperLiteActAsPerson', - 'foreignKey' => 'actor_co_person_id' + 'CoGrouperLiteWidget' => ['ActAsPerson'], + 'CoPerson' => [ + 'ActAsPerson' => [ + 'className' => 'ActAsPerson', + 'foreignKey' => 'co_person_id' ] ], - 'CoPerson' => [ - 'GrouperLiteActAsPerson' => [ - 'className' => 'GrouperLiteActAsPerson', + 'CoPerson' => [ + 'ActAsPerson' => [ + 'className' => 'ActAsPerson', 'foreignKey' => 'act_as_co_person_id' ] ] @@ -47,15 +47,11 @@ class GrouperLiteActAsPerson extends AppModel // Association rules from this model to other models public $belongsTo = [ - 'GrouperLiteWidget', 'ActAsCoPerson' => [ 'className' => 'CoPerson', 'foreignKey' => 'act_as_co_person_id' ], - 'ActorCoPerson' => [ - 'className' => 'CoPerson', - 'foreignKey' => 'actor_co_person_id' - ], + 'CoPerson', 'CoGrouperLiteWidget' ]; @@ -71,7 +67,7 @@ class GrouperLiteActAsPerson extends AppModel 'required' => true, 'allowEmpty' => false ], - 'actor_co_person_id' => [ + 'co_person_id' => [ 'rule' => 'numeric', 'required' => true, 'allowEmpty' => false diff --git a/Model/GrouperGroup.php b/Model/GrouperGroup.php index 6f14364..a8f55b8 100644 --- a/Model/GrouperGroup.php +++ b/Model/GrouperGroup.php @@ -554,9 +554,9 @@ public function isGroupMember(string $userId, string $groupName, array $cfg): bo } /** - * Determine if User can use the Grouper Template to create a Working Group. + * Determine if a User can use the Grouper Template to create a Working Group. * - * @param string $userId Id of User + * @param string $userId User ID * @return string T for True and F for False * @throws GrouperLiteWidgetException * diff --git a/Model/GrouperLiteWidget.php b/Model/GrouperLiteWidget.php index 5c38153..d5b8aa3 100644 --- a/Model/GrouperLiteWidget.php +++ b/Model/GrouperLiteWidget.php @@ -39,7 +39,7 @@ class GrouperLiteWidget extends AppModel { public $belongsTo = []; public $hasMany = [ - 'GrouperLiteActAsPerson' => ['dependent' => true] + 'ActAsPerson' => ['dependent' => true] ]; // Validation rules for table elements diff --git a/View/Elements/actAsPeopleAutocomplete.ctp b/View/Elements/actAsPeopleAutocomplete.ctp index 0bf351e..67a0f85 100644 --- a/View/Elements/actAsPeopleAutocomplete.ctp +++ b/View/Elements/actAsPeopleAutocomplete.ctp @@ -14,7 +14,6 @@ $suffix = Configure::read('debug') > 0 ? '?time=' . time() : ''; import Autocomplete from 'webroot ?>grouper_lite_widget/js/autocomplete.js'; import Loader from 'webroot ?>grouper_lite_widget/js/loader.js'; - // XXX Probably move this to comanage.js const provided = { collapsed: , optAction: "", @@ -78,6 +77,7 @@ $suffix = Configure::read('debug') > 0 ? '?time=' . time() : ''; api: { co: , glid: , + coPersonId: , mode: "", base: "webroot ?>grouper_lite_widget/grouper_groups", find: "webroot ?>grouper_lite_widget/grouper_groups/findSubscriber/co:/glid:", @@ -85,6 +85,7 @@ $suffix = Configure::read('debug') > 0 ? '?time=' . time() : ''; leave: "webroot ?>grouper_lite_widget/grouper_groups/leaveGroup/co:/glid:", remove: "webroot ?>grouper_lite_widget/grouper_groups/removeSubscriber/co:/glid:", add: "webroot ?>grouper_lite_widget/grouper_groups/addSubscriber/co:/glid:", + addActAs: "webroot ?>grouper_lite_widget/act_as_people/add/co:/glid:", group: "webroot ?>grouper_lite_widget/grouper_groups/groupSubscribers/co:/glid:", memberships: "webroot ?>grouper_lite_widget/grouper_groups/groupmemberapi/co:/glid:", managing: "webroot ?>grouper_lite_widget/grouper_groups/usermanagerapi/co:/glid:", @@ -94,13 +95,10 @@ $suffix = Configure::read('debug') > 0 ? '?time=' . time() : ''; } const app = Vue.createApp({ - provide: provided, data() { return { loading: false, - search: '', - subscribers: [], - disabled: [], + rawData: [] } }, components: { @@ -109,14 +107,54 @@ $suffix = Configure::read('debug') > 0 ? '?time=' . time() : ''; }, inject: ['txt', 'api'], methods: { - addUser({ name }) { - console.log('test add user') + async addUser(user) { + const { identifier: ident, label, value: id } = user; + this.loading = true; + + // Create form + const formData = new FormData(); + formData.append("act_as_co_person_id", id); + formData.append("co_person_id", this.api.coPersonId); + formData.append("co_grouper_lite_widget_id", this.api.glid); + + // Request URL + const urlString = window.location.protocol + "//" + window.location.host + this.api.addActAs; + const url = new URL(urlString); + + // AJAX Request + const request = new Request(url, { + headers: new Headers({ + 'X-Requested-With': 'XMLHttpRequest', + "Accept": "application/json", + }), + method: "POST", + body: formData + }); + + // Fire request + const resp = await (fetch(request).catch(error => generateFlash('Network Error', 'error'))); + + if (!resp.ok) { + generateFlash('Enabling ActAs User failed', 'error'); + let errorResponse = await resp.json(); + generateFlash(`${errorResponse.message}`, 'error'); + this.loading = false; + return + } + generateFlash('Act As User Enabled', 'success'); + this.rawData = await resp.json(); + this.loading = false; + // Force reload here }, } }); app.config.unwrapInjectedRef = true; + // For core configurations and texts globally + for (const [key, value] of Object.entries(provided)) { + app.provide( key, value); + } // Mount the component and provide a global reference for this app instance. app.mount("#-container"); From 86c469148397db53f0f830d9d5af677e8462459f Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Wed, 3 Apr 2024 13:51:05 +0300 Subject: [PATCH 43/60] Add,Delete act as user --- Controller/ActAsPeopleController.php | 94 ++++++++++++++++--- Controller/GrouperGroupsController.php | 23 ++++- Model/GrouperGroup.php | 30 ++++-- ...mplete.ctp => ActAsPeopleAutocomplete.ctp} | 9 +- View/Elements/ActionItem.ctp | 61 ++++++++++++ View/Elements/ActionSideBar.ctp | 2 + View/GrouperGroups/index.ctp | 37 ++------ webroot/css/co-grouper-base.css | 4 + 8 files changed, 209 insertions(+), 51 deletions(-) rename View/Elements/{actAsPeopleAutocomplete.ctp => ActAsPeopleAutocomplete.ctp} (97%) create mode 100644 View/Elements/ActionItem.ctp create mode 100644 View/Elements/ActionSideBar.ctp diff --git a/Controller/ActAsPeopleController.php b/Controller/ActAsPeopleController.php index eb872ff..71e5b07 100644 --- a/Controller/ActAsPeopleController.php +++ b/Controller/ActAsPeopleController.php @@ -76,8 +76,8 @@ public function beforeFilter() public function add(): void { - $this->layout = null; - $this->autoRender = false; + $this->request->allowMethod('ajax'); + $this->layout = 'ajax'; if(!$this->request->is('restful') && !$this->request->is('ajax')) { @@ -86,40 +86,110 @@ public function add(): void $data = $this->request->data; + $fullName = $data['fullname']; + unset($data['fullname']); + try { $ret = $this->ActAsPerson->saveAll($data); } catch(Exception $e) { $err = filter_var($e->getMessage(),FILTER_SANITIZE_SPECIAL_CHARS); + $this->Flash->set($err, ['key' => 'error']); } if($ret) { - // Reread the data so we account for any normalizations + $this->Api->restResultHeader(HttpStatusCodesEnum::HTTP_CREATED, 'Added'); + // I am registering a flash message. It will render after the frontend triggers a reload + $this->Flash->set("Act As {$fullName}" , ['key' => 'success']); + $args = []; $args['conditions']['ActAsPerson.id'] = $this->ActAsPerson->id; $args['contain'] = false; $data = $this->ActAsPerson->find('first', $args); - $this->Api->restResultHeader(201, "Added"); - $this->set('ActAsPerson', $this->ActAsPerson->id); - $this->response->body(json_encode($data['ActAsPerson'], JSON_THROW_ON_ERROR)); + + $resp = [ + 'ActAsPerson' => $data['ActAsPerson'] + ]; + $this->set(compact('resp')); + $this->set('_serialize', 'resp'); } else { $fs = $this->ActAsPerson->invalidFields(); if(!empty($fs)) { - $this->Api->restResultHeader(400, 'Invalid Fields'); - $this->set('invalid_fields', $fs); + $this->Api->restResultHeader(HttpStatusCodesEnum::HTTP_BAD_REQUEST, 'Invalid Fields'); + $this->Flash->set('Invalid Fields', ['key' => 'error']); + $this->set(compact('fs')); + $this->set('_serialize', 'fs'); } elseif ($e && isset(_txt('en.http.status.codes')[$e->getCode()]) ) { $this->Api->restResultHeader($e->getCode(), _txt('en.http.status.codes')[$e->getCode()]); if(!empty($e->getMessage())) { - $this->set('vv_error', $e->getMessage()); + $vv_error = $e->getMessage(); + $this->set(compact('vv_error')); + $this->set('_serialize', 'vv_error'); + $this->Flash->set($e->getMessage(), ['key' => 'error']); } } else { - $this->Api->restResultHeader(500, "Other Error"); + $this->Api->restResultHeader(HttpStatusCodesEnum::HTTP_INTERNAL_SERVER_ERROR, 'Other Error'); + $error = ErrorsEnum::Error; + $this->set(compact('error')); + $this->set('_serialize', 'error'); } } + } + + /** + * Handle an Act As Person Delete request. + * + * @since COmanage Registry v4.4.0 + */ + + public function delete():void + { + $this->layout = null; + $this->autoRender = false; + + if(!$this->request->is('delete') + || $this->request->is('ajax') + || $this->request->is('restful')) { + throw new RuntimeException('HTTP Method Not Allowed', HttpStatusCodesEnum::HTTP_METHOD_NOT_ALLOWED); + } + + if(empty($this->request->named['copersonid']) + || empty($this->request->named['act_as_copersonid'])) { + $this->log(__METHOD__ . '::message: Named Parameter missing', LOG_ERROR); + throw new BadRequestException('Named Parameter missing'); + } + + $redirect = [ + 'plugin' => 'grouper_lite_widget', + 'controller' => 'grouper_groups', + 'action' => $this->request->named['redirect_act'] ?? 'groupmember', + 'co' => $this->request->named['co'], + 'glid' => $this->request->named['glid'] + ]; + + try { + $conditions = [ + 'ActAsPerson.co_person_id' => $this->request->named['copersonid'], + 'ActAsPerson.act_as_co_person_id' => $this->request->named['act_as_copersonid'] + ]; + + $ret = $this->ActAsPerson->deleteAll($conditions, true, true); + } catch(Exception $e) { + $err = filter_var($e->getMessage(),FILTER_SANITIZE_SPECIAL_CHARS); + $this->Flash->set($err, ['key' => 'error']); + } + + // Set flash message + if($ret) { + $this->Flash->set(_txt('er.deleted-a', ['Person']) , ['key' => 'success']); + } else { + $this->Flash->set(_txt('er.delete'), ['key' => 'error']); + } - $this->response->send(); + // Redirect back to the plugin + $this->redirect($redirect); } @@ -137,6 +207,8 @@ public function add(): void public function isAuthorized(): array|bool { $roles = $this->Role->calculateCMRoles(); + $this->set('roles', $roles); + $pids = $this->parsePersonID($this->request->data); $cfg = $this->CoGrouperLiteWidget->getConfig(); diff --git a/Controller/GrouperGroupsController.php b/Controller/GrouperGroupsController.php index 354c425..6d75472 100644 --- a/Controller/GrouperGroupsController.php +++ b/Controller/GrouperGroupsController.php @@ -28,6 +28,7 @@ App::uses('Validator', 'Vendor/cakephp/Validation'); App::uses('CoGrouperLite', 'GrouperLiteWidget.Model/'); App::uses('GrouperGroup', 'GrouperLiteWidget.Model/'); +App::uses('ActAsPerson', 'GrouperLiteWidget.Model/'); App::uses('Identifier', 'Model'); /** @@ -45,6 +46,7 @@ class GrouperGroupsController extends GrouperLiteWidgetAppController public $uses = [ 'GrouperLiteWidget.GrouperGroup', 'GrouperLiteWidget.CoGrouperLiteWidget', + 'GrouperLiteWidget.ActAsPerson', 'Identifier', 'CoPerson' ]; @@ -152,6 +154,22 @@ public function beforeRender() { $roles = $this->Role->calculateCMRoles(); $this->set('vv_copersonid', $roles['copersonid'] ?? null); + $this->set('vv_picker_mode', PeoplePickerModeEnum::All); + + // Get the ActAs User Data + $co_person_id = $this->viewVars['roles']['copersonid']; + // Get the act as data from the database + $args = []; + $args['conditions']['ActAsPerson.co_person_id'] = $co_person_id; + $args['contain'] = false; + $act_as_record = $this->ActAsPerson->find('first', $args); + $this->set('vv_act_as_people', []); + if(!empty($act_as_record)) { + $act_as_person = $this->GrouperGroup->dataConstructForPicker($this->cur_co['Co']['id'], + PeoplePickerModeEnum::All, + [$act_as_record['ActAsPerson']['act_as_co_person_id']]); + $this->set('vv_act_as_people', $act_as_person); + } } /** @@ -423,12 +441,15 @@ public function index(): void public function isAuthorized(): array|bool { $roles = $this->Role->calculateCMRoles(); + $this->set('roles', $roles); + $pids = $this->parsePersonID($this->request->data); + $cfg = $this->CoGrouperLiteWidget->getConfig(); // Find the identifier $args = array(); $args['conditions']['Identifier.type'] = $cfg['CoGrouperLiteWidget']['identifier_type']; $args['conditions']['Identifier.status'] = SuspendableStatusEnum::Active; - $args['conditions']['Identifier.co_person_id'] = $roles['copersonid']; + $args['conditions']['Identifier.co_person_id'] = !empty($roles['copersonid']) ? $roles['copersonid'] : $pids['copersonid']; $args['contain'] = false; $identifiers = $this->Identifier->find('first', $args); diff --git a/Model/GrouperGroup.php b/Model/GrouperGroup.php index a8f55b8..ad83576 100644 --- a/Model/GrouperGroup.php +++ b/Model/GrouperGroup.php @@ -169,19 +169,15 @@ public function filteredMemberOfGroups(string $userId, array $cfg): array * * @return array Array of CO Person records * @since COmanage Registry v4.4.0 - * - * XXX Remove this as soon as the change is present in COmanage core - * */ public function findForPicker(int $coId, string $mode, ?string $term): array { $coPersonIds = []; $this->CoPerson = ClassRegistry::init('CoPerson'); - $this->Co = ClassRegistry::init('Co'); // jquery Autocomplete sends the search as url?term=foo if(!empty($term)) { - // Leverage model specific keyword search + // Leverage-model-specific keyword search // Note EmailAddress and Identifier don't support substring search foreach(array('Name', 'EmailAddress', 'Identifier') as $m) { @@ -197,7 +193,29 @@ public function findForPicker(int $coId, string $mode, ?string $term): array // We only do this when there are relatively small numbers of results to // avoid making a bunch of database queries early in the search. - $matches = array(); + return $this->dataConstructForPicker($coId, $term,$coPersonIds); + } + + /** + * Construct picker(like) response data based on mode and term + * + * @param integer $coId CO ID + * @param string $mode Search mode to apply filters for + * @param array $coPersonIds List of PersonIds + * + * @return array Array of CO Person records + * @since COmanage Registry v4.4.0 + */ + public function dataConstructForPicker(int $coId, string $mode, array $coPersonIds): array + { + if(empty($coPersonIds)) { + return []; + } + + $this->CoPerson = ClassRegistry::init('CoPerson'); + $this->Co = ClassRegistry::init('Co'); + + $matches = []; if(count($coPersonIds) > 100) { // We don't return large sets to avoid slow performance diff --git a/View/Elements/actAsPeopleAutocomplete.ctp b/View/Elements/ActAsPeopleAutocomplete.ctp similarity index 97% rename from View/Elements/actAsPeopleAutocomplete.ctp rename to View/Elements/ActAsPeopleAutocomplete.ctp index 67a0f85..26a951a 100644 --- a/View/Elements/actAsPeopleAutocomplete.ctp +++ b/View/Elements/ActAsPeopleAutocomplete.ctp @@ -78,7 +78,7 @@ $suffix = Configure::read('debug') > 0 ? '?time=' . time() : ''; co: , glid: , coPersonId: , - mode: "", + mode: "", base: "webroot ?>grouper_lite_widget/grouper_groups", find: "webroot ?>grouper_lite_widget/grouper_groups/findSubscriber/co:/glid:", join: "webroot ?>grouper_lite_widget/grouper_groups/joinGroup/co:/glid:", @@ -116,6 +116,7 @@ $suffix = Configure::read('debug') > 0 ? '?time=' . time() : ''; formData.append("act_as_co_person_id", id); formData.append("co_person_id", this.api.coPersonId); formData.append("co_grouper_lite_widget_id", this.api.glid); + formData.append("fullname", label); // Request URL const urlString = window.location.protocol + "//" + window.location.host + this.api.addActAs; @@ -141,9 +142,11 @@ $suffix = Configure::read('debug') > 0 ? '?time=' . time() : ''; this.loading = false; return } - generateFlash('Act As User Enabled', 'success'); + // generateFlash('Act As User Enabled', 'success'); this.rawData = await resp.json(); - this.loading = false; + console.log(this.rawData) + this.loading = false + window.location.reload(); // Force reload here }, } diff --git a/View/Elements/ActionItem.ctp b/View/Elements/ActionItem.ctp new file mode 100644 index 0000000..518ef4d --- /dev/null +++ b/View/Elements/ActionItem.ctp @@ -0,0 +1,61 @@ + + + + +
    +
    +
    + +
    + +
    + +
    +
    + Form->create('ActAsPeople', [ + 'id' => 'ActAsPeopleDeleteForm' . $person['value'], + 'inputDefaults' => [ + 'label' => false, + 'div' => false + ], + 'url' => [ + 'plugin' => 'grouper_lite_widget', + 'controller' => 'act_as_people', + 'action' => 'delete', + 'co' => $vv_coid, + 'glid' => $vv_config['CoGrouperLiteWidget']['id'], + 'act_as_copersonid' => $person['value'], + 'copersonid' => $vv_copersonid + ], + 'type' => 'delete' + ]); + $options = [ + 'label' => 'Disable', + 'onclick' => 'javascript:handleDisable(event)', + 'class' => 'btn btn-grouper btn-fit btn-block btn-danger btn-sm m-1 text-nowrap member-del-btn', + ]; + echo $this->Form->end($options); + ?> +
    diff --git a/View/Elements/ActionSideBar.ctp b/View/Elements/ActionSideBar.ctp new file mode 100644 index 0000000..a4abe2d --- /dev/null +++ b/View/Elements/ActionSideBar.ctp @@ -0,0 +1,2 @@ + 0 ? '?time=' . time() : '';
    - element('actAsPeopleAutocomplete', + element('ActAsPeopleAutocomplete', compact('vv_config', 'vv_coid', 'vv_is_user_owner', 'htmlId') )?> -
    - -
    -
    -
    - John Doe -
    - -
    - Identifier (I2CollabPN): nickmastoris.mastoris@at.in... -
    -
    - - -
    + +
    + + element('ActionItem', compact('person')) ?> + +
    diff --git a/webroot/css/co-grouper-base.css b/webroot/css/co-grouper-base.css index 836c05c..f501f8b 100644 --- a/webroot/css/co-grouper-base.css +++ b/webroot/css/co-grouper-base.css @@ -123,6 +123,10 @@ animation: fadeInHalf 0.9s -0.3s infinite reverse; } +.grouper_groups .person-item { + overflow-wrap: anywhere; +} + .popover { max-width: 400px; } From 9c121b4adaf50c78b2d6181be5b183e368f6554b Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Wed, 3 Apr 2024 14:06:15 +0300 Subject: [PATCH 44/60] Fix autocomplete unique id --- View/Elements/ActAsPeopleAutocomplete.ctp | 3 +-- webroot/js/autocomplete.js | 11 +++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/View/Elements/ActAsPeopleAutocomplete.ctp b/View/Elements/ActAsPeopleAutocomplete.ctp index 26a951a..46d5180 100644 --- a/View/Elements/ActAsPeopleAutocomplete.ctp +++ b/View/Elements/ActAsPeopleAutocomplete.ctp @@ -142,12 +142,11 @@ $suffix = Configure::read('debug') > 0 ? '?time=' . time() : ''; this.loading = false; return } - // generateFlash('Act As User Enabled', 'success'); this.rawData = await resp.json(); console.log(this.rawData) this.loading = false - window.location.reload(); // Force reload here + window.location.reload(); }, } }); diff --git a/webroot/js/autocomplete.js b/webroot/js/autocomplete.js index da566ce..50703f2 100644 --- a/webroot/js/autocomplete.js +++ b/webroot/js/autocomplete.js @@ -50,6 +50,9 @@ export default { // The minimum length that i start search is 3. So we only enable the button when // the input text value has at least three characters return this.activeBtn || (this.search.length < 3) + }, + autcompleteId() { + return `autocomplete-search-container-${this.action}` } }, mounted(el) { @@ -57,7 +60,7 @@ export default { this.url = `${this.api.find}?co=${this.api.co}&mode=${this.api.mode}&page=${this.page}&limit=${this.limit}` input.autocomplete({ source: ( request, response ) => { - $("#grouper-search-container .co-loading-mini").show(); + $(`#autocomplete-search-container-${this.action} .co-loading-mini`).show(); $.ajax({ url: this.url, type: 'GET', @@ -67,11 +70,11 @@ export default { term: request.term }, success: function (data) { - $("#grouper-search-container .co-loading-mini").hide(); + $(`#autocomplete-search-container-${this.action} .co-loading-mini`).hide(); response( data ); }, error: function(data) { - $("#grouper-search-container .co-loading-mini").hide(); + $(`#autocomplete-search-container-${this.action} .co-loading-mini`).hide(); console.log('Autocomplete ajax error:', data) generateFlash('Find action failed', 'error'); } @@ -99,7 +102,7 @@ export default { }) }, template: /*html*/` -
    +
    From 864a2aa2ad54f36b79750a5cf141ba7b7c574c87 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Wed, 3 Apr 2024 15:52:21 +0300 Subject: [PATCH 45/60] Fix autocomplete unique id --- webroot/js/autocomplete.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/webroot/js/autocomplete.js b/webroot/js/autocomplete.js index 50703f2..f6e05b5 100644 --- a/webroot/js/autocomplete.js +++ b/webroot/js/autocomplete.js @@ -57,6 +57,7 @@ export default { }, mounted(el) { const input = $(this.$el).find(`#${this.toKebabCase(this.action)}-input`); + const action = this.action this.url = `${this.api.find}?co=${this.api.co}&mode=${this.api.mode}&page=${this.page}&limit=${this.limit}` input.autocomplete({ source: ( request, response ) => { @@ -70,11 +71,11 @@ export default { term: request.term }, success: function (data) { - $(`#autocomplete-search-container-${this.action} .co-loading-mini`).hide(); + $(`#autocomplete-search-container-${action} .co-loading-mini`).hide(); response( data ); }, error: function(data) { - $(`#autocomplete-search-container-${this.action} .co-loading-mini`).hide(); + $(`#autocomplete-search-container-${action} .co-loading-mini`).hide(); console.log('Autocomplete ajax error:', data) generateFlash('Find action failed', 'error'); } From 8bd174c4216e5628fd61bc7852fc0104e9ed6cb5 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Wed, 3 Apr 2024 16:35:43 +0300 Subject: [PATCH 46/60] Move code at sidebar.ctp file --- Lib/lang.php | 4 ++- View/Elements/ActAsPeopleAutocomplete.ctp | 3 ++ View/Elements/ActionSideBar.ctp | 33 ++++++++++++++++- View/GrouperGroups/index.ctp | 43 ++++++----------------- 4 files changed, 48 insertions(+), 35 deletions(-) diff --git a/Lib/lang.php b/Lib/lang.php index 22fab32..11bccc5 100644 --- a/Lib/lang.php +++ b/Lib/lang.php @@ -126,8 +126,10 @@ 'pl.grouperlite.action.close' => 'Close', 'pl.grouperlite.action.clear' => 'Clear', 'pl.grouperlite.action.add-user' => 'Add', + 'pl.grouperlite.action.act-as' => 'Act As', 'pl.grouperlite.action.remove-user' => 'Remove', - 'pl.grouperlite.action.enable-act-as' => 'Enable', + 'pl.grouperlite.action.enable' => 'Enable', + 'pl.grouperlite.action.find' => 'Find', 'pl.grouperlite.message.user-not-found-error' => 'Error: User not found.', 'pl.grouperlite.message.user-not-added-error' => 'Error: Unable to add user.', 'pl.grouperlite.message.user-not-removed-error' => 'Error: Unable to remove user.', diff --git a/View/Elements/ActAsPeopleAutocomplete.ctp b/View/Elements/ActAsPeopleAutocomplete.ctp index 46d5180..c650022 100644 --- a/View/Elements/ActAsPeopleAutocomplete.ctp +++ b/View/Elements/ActAsPeopleAutocomplete.ctp @@ -21,6 +21,9 @@ $suffix = Configure::read('debug') > 0 ? '?time=' . time() : ''; url: "", grouperUrl: "", view: "action ?>", + hasActAs: , + actAsPerson: , + permissions: , txt: { adhocHeading: "", wgHeading: "", diff --git a/View/Elements/ActionSideBar.ctp b/View/Elements/ActionSideBar.ctp index a4abe2d..3c8898d 100644 --- a/View/Elements/ActionSideBar.ctp +++ b/View/Elements/ActionSideBar.ctp @@ -1,2 +1,33 @@ - + +
    + diff --git a/View/GrouperGroups/index.ctp b/View/GrouperGroups/index.ctp index 00a2e3e..0dc3b5e 100644 --- a/View/GrouperGroups/index.ctp +++ b/View/GrouperGroups/index.ctp @@ -71,6 +71,9 @@ $suffix = Configure::read('debug') > 0 ? '?time=' . time() : ''; url: "", grouperUrl: "", view: "action ?>", + hasActAs: , + actAsPerson: , + permissions: , txt: { adhocHeading: "", wgHeading: "", @@ -124,7 +127,6 @@ $suffix = Configure::read('debug') > 0 ? '?time=' . time() : ''; JSON_THROW_ON_ERROR) ?>, }, api: { - permissions: , co: , glid: , mode: "", @@ -158,37 +160,12 @@ $suffix = Configure::read('debug') > 0 ? '?time=' . time() : '';
    - + element('ActionSideBar', + compact('vv_config', + 'vv_act_as_people', + 'vv_coid', + 'vv_is_user_owner', + 'htmlId') + ) ?>
    \ No newline at end of file From b0042c77bdddbe46a0673746f368ebc29658e024 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Wed, 3 Apr 2024 18:01:12 +0300 Subject: [PATCH 47/60] Add focus on people autocomplete on collapse open --- View/Elements/ActAsPeopleAutocomplete.ctp | 2 +- View/Elements/ActionSideBar.ctp | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/View/Elements/ActAsPeopleAutocomplete.ctp b/View/Elements/ActAsPeopleAutocomplete.ctp index c650022..d147ae9 100644 --- a/View/Elements/ActAsPeopleAutocomplete.ctp +++ b/View/Elements/ActAsPeopleAutocomplete.ctp @@ -44,7 +44,7 @@ $suffix = Configure::read('debug') > 0 ? '?time=' . time() : ''; }, members: "", email: "", - enableActAs: "", + enableActAs: "", close: "", remove: "", addUser: "", diff --git a/View/Elements/ActionSideBar.ctp b/View/Elements/ActionSideBar.ctp index 3c8898d..8658819 100644 --- a/View/Elements/ActionSideBar.ctp +++ b/View/Elements/ActionSideBar.ctp @@ -1,3 +1,11 @@ + +
    @@ -38,7 +39,7 @@ export default { v-if="$attrs.onJoinGroup" @click="$emit('joinGroup', group)" class="btn btn-sm btn-block text-nowrap m-1 btn-success" type="button" - :disabled="group.loading"> + :disabled="group.loading || this.other.hasActAs"> {{ txt.join }}   @@ -47,7 +48,7 @@ export default { v-if="$attrs.onLeaveGroup" @click="$emit('leaveGroup', group)" class="btn btn-sm btn-block text-nowrap m-1 btn-danger" type="button" - :disabled="group.loading"> + :disabled="group.loading || this.other.hasActAs"> {{ txt.leave }} @@ -57,6 +58,7 @@ export default { class="btn btn-grouper btn-block btn-primary btn-sm m-1 text-nowrap members-btn" @click="$emit('showSubscribers', group)" :data-id="encodeURIComponent(group.name)" + :disabled="this.other.hasActAs" :data-name="group.displayExtension">{{ txt.members }} @@ -64,7 +66,7 @@ export default { v-if="$attrs.onRemoveUser" class="btn btn-sm btn-block text-nowrap m-1 btn-danger" type="button" @click="$emit('removeUser', group)" - :disabled="group.loading" + :disabled="group.loading || this.other.hasActAs" :data-id="encodeURIComponent(group.name)" :data-name="group.displayExtension"> {{ txt.remove }} diff --git a/webroot/js/members.js b/webroot/js/members.js index cd15a2f..ac71a1c 100644 --- a/webroot/js/members.js +++ b/webroot/js/members.js @@ -34,7 +34,7 @@ export default { remove: Boolean, default: false }, - inject: ['txt', 'api'], + inject: ['txt', 'api', 'other'], components: { Loader, Autocomplete @@ -163,7 +163,8 @@ export default {
    - +
    @@ -182,7 +183,9 @@ export default { {{ subscriber.id }} diff --git a/webroot/js/nested-table.js b/webroot/js/nested-table.js index eff276b..a60ff9a 100644 --- a/webroot/js/nested-table.js +++ b/webroot/js/nested-table.js @@ -39,7 +39,10 @@ export default {
    -
    -