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..506f630 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,8 +34,10 @@ REFERENCES cm_co_dashboard_widgets(id) + + @@ -44,7 +46,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..1967991 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,21 @@ 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 +175,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 +190,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 +205,19 @@ public function addSubscriber() ]; try { - $resultAdd = $this->GrouperGroup->addMemberToGroup($scope, $this->userId); - + $resultAdd = $this->GrouperGroup->addGrouperGroupMember($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 +229,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 +337,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 +352,20 @@ public function removeSubscriber() ]; try { - $resultRemove = $this->GrouperGroup->removeMemberToGroup($scope, $this->userId); + $resultRemove = $this->GrouperGroup->removeGrouperGroupMember($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 +373,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 +381,30 @@ 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 +414,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 +422,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 +467,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 +501,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 +533,20 @@ 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->CoGrouperLiteWidget->getConfig())) { + $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 +563,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 = ''; } @@ -743,44 +596,21 @@ public function leaveGroup() function isAuthorized() { $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; - /** - * 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'); + $identifiers = $this->Identifier->find('first', $args); + if(!empty($identifiers) + && is_array($identifiers) + && isset($identifiers['Identifier']['identifier']) + ) { + $this->userId = $identifiers['Identifier']['identifier']; } - */ - // Default End =============================================== - - /** - * Customized Crosswalk from login-id to Grouper Username - */ - // Custom Begin =============================================== - - $username = $this->Session->read('Auth.User.username'); - - 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); - } - - // Custom End =============================================== - // Determine what operations this user can perform // Construct the permission set for this user, which will also be passed to the view. @@ -810,48 +640,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 +666,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']) + || empty($cfg['CoGrouperLiteWidget']['conn_url']) + || empty($cfg['CoGrouperLiteWidget']['conn_user']) + || empty($cfg['CoGrouperLiteWidget']['conn_pass']) + || empty($cfg['CoGrouperLiteWidget']['conn_ver']) + ) { + throw new GrouperLiteWidgetException('No GrouperLite instance captured'); } - $connUrl = CakeSession::read('Plugin.Grouper.Api.url'); - $connVer = CakeSession::read('Plugin.Grouper.Api.version'); - - $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->setServiceUrl($cfg['CoGrouperLiteWidget']['conn_url'], $cfg['CoGrouperLiteWidget']['conn_ver']); + $this->http->setUser($cfg['CoGrouperLiteWidget']['conn_user']); + $this->http->setPassword($cfg['CoGrouperLiteWidget']['conn_pass']); + // Assume json content responses + $this->http->setHeader(['Content-Type' => 'application/json', 'Accept' => 'application/json']); } /** - * Get Groups that User is a member of from Grouper. - * - * Note: Params added at end make sure that the groups returned can only be viewed by the member logged into - * Grouper Lite + * Add a member to a specific Grouper Group * * @param array $queryData Array of conditions for querying - * @return array Membership records that User is a member of in Grouper - * @throws GrouperLiteException + * + * @return string Requests success or not + * @throws GrouperLiteWidgetException|JsonException */ - public function getGrouperMemberOfGroups(array $queryData) + public function addGrouperGroupMember(array $queryData): string { + $groupName = $queryData['groupName']; + $actionEndpoint = "/groups/$groupName/members"; //Build request logic - $userId = $queryData['userId']; - $connectionUrl = "{$this->config['fullUrl']}/subjects/$userId/groups?"; - $connectionUrl .= "wsLiteObjectType=WsRestGetGroupsLiteRequest&actAsSubjectId=$userId"; + $usersToAdd = [ + 'WsRestAddMemberRequest' => [ + 'subjectLookups' => [ + ['subjectId' => $queryData['addUserId']], + ], + 'replaceAllExisting' => 'F', + 'actAsSubjectLookup' => [ + 'subjectId' => $queryData['userId'] + ] + ] + ]; try { - $results = $this->http->sendRequest('GET', $connectionUrl); - - // Parse out relevant records to send front end - if (isset($results['WsGetGroupsLiteResult']['wsGroups']) && $results['WsGetGroupsLiteResult']['wsGroups'] != NULL) { - return $results['WsGetGroupsLiteResult']['wsGroups']; - } + $results = $this->http->sendRequest('PUT', + $actionEndpoint, + json_encode($usersToAdd, JSON_THROW_ON_ERROR)); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } - return array(); + return $results['WsAddMemberResults']['results'][0]['resultMetadata']['resultCode'] ?? ''; } /** - * For Optin groups, calls Grouper WS to join or leave a group + * Method used to CREATE a new Group in Grouper via the Working Group Template method. + * + * @param array $queryData Array of conditions and data adding new Grouper Group + * @return array status and error message, if applicable + * @throws GrouperLiteWidgetException * - * @param array $queryData Array of conditions for querying - * @return bool True if join or leave successful, False if not - * @throws GrouperLiteException */ - public function grouperGroupLeaveOrJoin(array $queryData) + public function createGroupWithTemplate(array $queryData): array { + //Currently, only supporting create group, need to test if update a group will work! - $groupName = $queryData['groupName']; + $data = $queryData['data']; $userId = $queryData['userId']; - $groupLeaveOrJoin = $queryData['LeaveJoin']; - $groupName = urlencode($groupName); - - if ($groupLeaveOrJoin == "Leave") { - $memberRequest = "WsRestDeleteMemberRequest"; - $resultResponse = 'WsDeleteMemberResults'; - $resultGroup = 'wsGroup'; - } elseif ($groupLeaveOrJoin == "Join") { - $memberRequest = "WsRestAddMemberRequest"; - $resultResponse = 'WsAddMemberResults'; - $resultGroup = 'wsGroupAssigned'; - } else { - CakeLog::write('error', __METHOD__ . ": Option of $groupLeaveOrJoin is not supported"); - throw new GrouperLiteException("Received option of $groupLeaveOrJoin which is not supported"); + + //need to take out SympaDomain if domain not being created. + if ($data['gsh_input_isSympa'] == 'false') { + unset($data['gsh_input_sympaDomain'], $data['gsh_input_isSympaModerated']); } //Build request logic - $groupCommand = array( - $memberRequest => array( - "actAsSubjectLookup" => array( - "subjectId" => $userId - ), - "replaceAllExisting" => "F", - "subjectLookups" => array( - array("subjectId" => $userId) - ) - ) - ); + $inputFields = []; + foreach ($data as $key => $value) { + $inputFields[] = ['name' => $key, 'value' => $value]; + } - $this->http->setHeader(array('Content-Type' => 'application/json', 'Accept' => 'application/json')); - $connectionUrl = "{$this->config['fullUrl']}/groups/$groupName/members"; + $groupToSave = [ + 'WsRestGshTemplateExecRequest' => [ + 'gshTemplateActAsSubjectLookup' => [ + 'subjectSourceId' => 'ldap', + 'subjectId' => $userId + ], + 'ownerStemLookup' => [ + 'stemName' => 'ref:incommon-collab' + ], + 'ownerType' => 'stem', + 'configId' => 'createNewWorkingGroup', + 'inputs' => $inputFields + ] + ]; + + $actionEndpoint = "/gshTemplateExec"; + $status = true; + $message = ''; try { - $results = $this->http->sendRequest('PUT', $connectionUrl, json_encode($groupCommand)); - - if (isset($results[$resultResponse][$resultGroup]) && $results[$resultResponse][$resultGroup] != NULL) { - $groupAssigned = $results[$resultResponse][$resultGroup]['name']; - if ($groupAssigned == urldecode($groupName)) { - return true; - } - } + $results = $this->http->sendRequest('POST', + $actionEndpoint, + json_encode($groupToSave)); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } - return false; + 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'); } /** - * Gets all available Optin/OptOut groups in Grouper * - * Returns Optin/OptOut groups that can be joined/left + * For creating/updating Ad-Hoc groups not using the Grouper Template format * - * @param array $queryData Array of conditions for querying - * @return array Optin groups from Grouper - * @throws GrouperLiteException + * 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 GrouperLiteWidgetException|JsonException */ - public function getOptionalGroups(array $queryData) + public function createUpdateGroup(array $queryData) { - try { - $results = $this->useMembershipUrl($queryData); + $groupName = htmlentities($queryData['name']); + $stemName = htmlentities($queryData['stem']); + $userId = $queryData['userId']; + $groupDescription = htmlentities($queryData['description']); - if (isset($results['WsGetMembershipsResults']['wsGroups']) && $results['WsGetMembershipsResults']['wsGroups'] != NULL) { - return $results['WsGetMembershipsResults']['wsGroups']; - } + //Group name may be in "friendly" format, so need to do some small conversions before saving. + $newGroupName = ucfirst(strtolower($groupName)); + $newGroupName = str_replace(' ', '', $newGroupName); + + $newGroupToSave = "$stemName:$newGroupName"; + + //Build request logic + $groupToSave = [ + 'WsRestGroupSaveRequest' => [ + 'actAsSubjectLookup' => [ + 'subjectId' => $userId + ], + 'wsGroupToSaves' => [ + [ + 'wsGroup' => [ + 'description' => $groupDescription, + 'name' => $newGroupToSave, + 'displayExtension' => $groupName + ], + 'wsGroupLookup' => [ + 'groupName' => $groupDescription + ] + ] + ] + ] + ]; + $actionEndpoint = "/groups"; + + try { + $results = $this->http->sendRequest('POST', + $actionEndpoint, + json_encode($groupToSave, JSON_THROW_ON_ERROR)); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } - return array(); + + return isset($results['WsGroupSaveResults']['results']['resultMetadata']['resultCode']) + && $results['WsGroupSaveResults']['results']['resultMetadata']['resultCode'] === GrouperResultCodesEnum::SUCCESS; } /** - * Gets all groups in Grouper where user is an admin/owner or has update privs * - * @param array $queryData Array of conditions for querying - * @return array Array of groups from Grouper - * @throws GrouperLiteException + * 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 GrouperLiteWidgetException|JsonException * */ - public function getOwnedGroups(array $queryData) + public function deleteGroupWithTemplate(array $queryData) { + $workingGroupExt = $queryData['workingGroupExt']; + $userId = $queryData['userId']; + + $groupToDelete = [ + 'WsRestGshTemplateExecRequest' => [ + 'gshTemplateActAsSubjectLookup' => [ + 'subjectSourceId' => 'ldap', + 'subjectId' => $userId + ], + 'ownerStemLookup' => [ + 'stemName' => 'ref:incommon-collab' + ], + 'ownerType' => 'stem', + 'configId' => 'createWorkingGroup', + 'inputs' => [ + [ + 'name' => 'gsh_input_workingGroupExtension', + 'value' => $workingGroupExt + ] + ] + ] + ]; + + $actionEndpoint = "/gshTemplateExec"; try { - $queryData['groupType'] = 'admin'; - $resultsAdmin = $this->useMembershipUrl($queryData); + $results = $this->http->sendRequest('POST', + $actionEndpoint, + json_encode($groupToDelete, JSON_THROW_ON_ERROR)); + } catch (Exception $e) { + CakeLog::write('error', __METHOD__ . ': An error occurred'); + throw $e; + } - $queryData['groupType'] = 'update'; - $resultsUpdate = $this->useMembershipUrl($queryData); + if (isset($results['WsGshTemplateExecResult']['resultMetadata']['resultCode']) && stripos( + $results['WsGshTemplateExecResult']['resultMetadata']['resultCode'], + 'SUCCESS', + 0 + ) !== false) { + return true; + } - if (isset($resultsAdmin['WsGetMembershipsResults']['wsGroups']) && $resultsAdmin['WsGetMembershipsResults']['wsGroups'] != NULL) { - $admins = $resultsAdmin['WsGetMembershipsResults']['wsGroups']; - } else { - $admins = array(); - } + return false; + } - if (isset($resultsUpdate['WsGetMembershipsResults']['wsGroups']) && $resultsUpdate['WsGetMembershipsResults']['wsGroups'] != NULL) { - $updaters = $resultsUpdate['WsGetMembershipsResults']['wsGroups']; - } else { - $updaters = array(); - } + /** + * 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 GrouperLiteWidgetException|JsonException + */ + public function getGrouperGroupInfo(array $queryData) + { + $groupName = $queryData['groupName']; + $groupInfo = []; + + //Build request logic + $stemToFind = [ + 'WsRestGetAttributeAssignmentsRequest' => [ + 'attributeAssignType' => 'group', + 'includeAssignmentsOnAssignments' => 'T', + 'wsOwnerGroupLookups' => [ + [ + 'groupName' => $groupName, + ] + ] + ] + ]; + $actionEndpoint = "/attributeAssignments"; - return $this->removeDuplicates($admins, $updaters); + try { + $results = $this->http->sendRequest('POST', + $actionEndpoint, + json_encode($stemToFind, JSON_THROW_ON_ERROR)); } 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'] ?? []; + + return $groupInfo; } /** - * Removes duplicates where the user is the owner and the updater of the group. Just one line instead of two. + * Get Groups that User is a member of from Grouper. * - * @param array $arrOne - * @param array $arrTwo - * @return 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 $userId + * + * @return array Membership records that User is a member of in Grouper + * + * @throws GrouperLiteWidgetException */ - public function removeDuplicates(array $arrOne, array $arrTwo) + public function getGrouperMemberOfGroups(string $userId) { - - //Determine which array is bigger and use as base - $countOne = count($arrOne); - $countTwo = count($arrTwo); - if ($countOne >= $countTwo) { - $arrL = $arrOne; - $arrS = $arrTwo; - } else { - $arrL = $arrTwo; - $arrS = $arrOne; + if(empty($userId)) { + return []; } - foreach ($arrL as $large) { - foreach ($arrS as $key => $val) { - if ($large['uuid'] == $val['uuid']) { - unset($arrS[$key]); - } - } + $actionEndpoint = "/subjects/{$userId}/groups?" + . 'wsLiteObjectType=WsRestGetGroupsLiteRequest' + . "&actAsSubjectId={$userId}"; + try { + $results = $this->http->sendRequest('GET', $actionEndpoint); + } catch (Exception $e) { + CakeLog::write('error', __METHOD__ . ': An error occurred'); + throw $e; } - return array_merge_recursive($arrL, $arrS); + return $results['WsGetGroupsLiteResult']['wsGroups'] ?? []; } /** * 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 = [ + 'WsRestGetMembersRequest' => [ + 'actAsSubjectLookup' => [ + 'subjectId' => $queryData['userId'] + ], + 'wsGroupLookups' => [ + ['groupName' => $queryData['groupName']] + ], + 'subjectAttributeNames' => ['name'] + ] + ]; + + $actionEndpoint = "/groups"; try { - //Build request logic - $usersToShow = array( - "WsRestGetMembersRequest" => array( - "actAsSubjectLookup" => array( - "subjectId" => $queryData['userId'] - ), - "wsGroupLookups" => array( - array("groupName" => $queryData['groupName']) - ), - "subjectAttributeNames" => array( - "name" - ) - ) - ); - - $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)); - - // 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]['wsSubjects']) && $results['WsGetMembersResults']['results'][0]['wsSubjects'] != NULL) { - return $results['WsGetMembersResults']['results'][0]['wsSubjects']; - } + $results = $this->http->sendRequest('POST', + $actionEndpoint, + json_encode($usersToShow, JSON_THROW_ON_ERROR)); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } - return array(); + // Parse out relevant records to send front end + if(isset($results['WsGetMembersResults']['results'][0]['resultMetadata']['resultCode']) + && $results['WsGetMembersResults']['results'][0]['resultMetadata']['resultCode'] == 'GROUP_NOT_FOUND') { + return [ + [ + 'sourceId' => 'NoAccess', + 'name' => '', + 'id' => '' + ] + ]; + } + + return $results['WsGetMembersResults']['results'][0]['wsSubjects'] ?? []; } /** - * Add a member to a specific Grouper Group + * Gets all available Optin/OptOut groups in Grouper + * + * Returns Optin/OptOut groups that can be joined/left * * @param array $queryData Array of conditions for querying - * @return string Requests success or not - * @throws GrouperLiteException + * @return array Optin groups from Grouper + * @throws GrouperLiteWidgetException + * */ - public function addMemberToGroup(array $queryData) + public function getOptionalGroups(array $queryData) { try { - $groupName = $queryData['groupName']; - - //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"; - - $results = $this->http->sendRequest('PUT', $connectionUrl, json_encode($usersToAdd)); - - // Parse out relevant records to send front end - if (isset($results['WsAddMemberResults']['results'][0]['resultMetadata']) && $results['WsAddMemberResults']['results'][0]['resultMetadata'] != NULL) { - return $results['WsAddMemberResults']['results'][0]['resultMetadata']['resultCode']; - } + $results = $this->useMembershipUrl($queryData); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } - - return ""; + return $results['WsGetMembershipsResults']['wsGroups'] ?? []; } /** - * Remove a member from a specific Grouper Group + * Gets all groups in Grouper where user is an admin/owner or has update privs * * @param array $queryData Array of conditions for querying - * @return string Requests success or not - * @throws GrouperLiteException + * @return array Array of groups from Grouper + * @throws GrouperLiteWidgetException + * */ - public function removeMemberToGroup(array $queryData) + public function getOwnedGroups(array $queryData) { - try { - $groupName = $queryData['groupName']; - - //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)); - - // Parse out relevant records to send front end - if (isset($results['WsDeleteMemberResults']['results'][0]['resultMetadata']) && $results['WsDeleteMemberResults']['results'][0]['resultMetadata'] != NULL) { - return $results['WsDeleteMemberResults']['results'][0]['resultMetadata']['resultCode']; - } + $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)); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } - return ""; + return $this->removeDuplicates($resultsAdmin['WsGetMembershipsResults']['wsGroups'] ?? [], + $resultsUpdate['WsGetMembershipsResults']['wsGroups'] ?? []); } /** - * Used for requests made to Membership endpoint in Grouper WS + * Potential use was for creating adhoc group by a user, not associated to WG. * - * @param array $queryData Array of conditions for querying - * @return array Group records associated to calling method - * @throws GrouperLiteException + * Gets all Stems/Folders where User is admin/owner * - * @see getOwnedStems() - * @see getOptinGroups() - * @see getOptOutGroups() - * @see getOwnedGroups() + * @param array $queryData Array of conditions for querying + * @return array Array of Stems/Folders from Grouper + * @throws GrouperLiteWidgetException */ - private function useMembershipUrl(array $queryData) + public function getOwnedStems(array $queryData) { - $groupType = $queryData['groupType']; - $userId = $queryData['userId']; - - if ($groupType == 'optins' || $groupType == 'optouts') { - $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"); - } - - 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) - ) - ) - ); - } - - $this->http->setHeader(array('Content-Type' => 'application/json', 'Accept' => 'application/json')); - $connectionUrl = "{$this->config['fullUrl']}/memberships"; + $queryData['groupType'] = 'stemAdmin'; try { - return $this->http->sendRequest('POST', $connectionUrl, json_encode($groupsToShow)); + $results = $this->useMembershipUrl($queryData); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } + return $results['WsGetMembershipsResults']['wsStems'] ?? []; } /** - * Method used to CREATE a new Group in Grouper via the Working Group Template method. + * For Optin groups, calls Grouper WS to join a group * - * @param array $queryData Array of conditions and data adding new Grouper Group - * @return array status and error message, if applicable - * @throws GrouperLiteException + * @param string $userId + * @param string $groupName * + * @return bool True if join or leave successful, False if not + * @throws GrouperLiteWidgetException + * @throws JsonException */ - public function createGroupWithTemplate(array $queryData) + public function joinGrouperGroup(string $userId, string $groupName) { - //Currently, only supporting create group, need to test if update a group will work! - - $data = $queryData['data']; - $userId = $queryData['userId']; - - //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']); + if(empty($userId) || empty($groupName)) { + return false; } //Build request logic - $inputFields = array(); - foreach ($data as $key => $value) { - $inputFields[] = array('name' => $key, 'value' => $value); - } - - $groupToSave = array( - "WsRestGshTemplateExecRequest" => array( - "gshTemplateActAsSubjectLookup" => array( - "subjectSourceId" => "ldap", - "subjectId" => $userId - ), - "ownerStemLookup" => array( - "stemName" => "ref:incommon-collab" - ), - "ownerType" => "stem", - "configId" => "createNewWorkingGroup", - "inputs" => $inputFields - ) - ); + $groupCommand = [ + 'WsRestAddMemberRequest' => [ + 'actAsSubjectLookup' => [ + 'subjectId' => $userId + ], + 'replaceAllExisting' => 'F', + 'subjectLookups' => [ + ['subjectId' => $userId] + ] + ] + ]; + + $actionEndpoint = "/groups/{$groupName}/members"; - $this->http->setHeader(array('Content-Type' => 'application/json', 'Accept' => 'application/json')); - $connectionUrl = "{$this->config['fullUrl']}/gshTemplateExec"; - - $status = true; - $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!"); - } - } + $results = $this->http->sendRequest('PUT', + $actionEndpoint, + json_encode($groupCommand, JSON_THROW_ON_ERROR)); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } - return array( - 'status' => $status, - 'message' => $message - ); - + return isset($results['WsAddMemberResults']['wsGroupAssigned']) + && $results['WsAddMemberResults']['wsGroupAssigned']['name'] == urldecode($groupName); } /** - * ======================== NOT BEING USED ======================== - * - * Method used to DELETE a Group in Grouper via the Template method. + * For Optin groups, calls Grouper WS to join or leave a group * - * @param array $queryData Array of conditions and data adding new Grouper Group - * @return bool True if deleted successfully - * @throws GrouperLiteException + * @param array $queryData Array of conditions for querying * + * @return bool True if join or leave successful, False if not + * @throws GrouperLiteWidgetException|JsonException */ - public function deleteGroupWithTemplate(array $queryData) + public function leaveGrouperGroup(string $userId, string $groupName) { + if(empty($userId) || empty($groupName)) { + return false; + } - $workingGroupExt = $queryData['workingGroupExt']; - $userId = $queryData['userId']; - - $groupToDelete = array( - "WsRestGshTemplateExecRequest" => array( - "gshTemplateActAsSubjectLookup" => array( - "subjectSourceId" => "ldap", - "subjectId" => $userId - ), - "ownerStemLookup" => array( - "stemName" => "ref:incommon-collab" - ), - "ownerType" => "stem", - "configId" => "createWorkingGroup", - "inputs" => array( - array( - "name" => "gsh_input_workingGroupExtension", - "value" => $workingGroupExt - ) - ) - ) - ); - - $this->http->setHeader(array('Content-Type' => 'application/json', 'Accept' => 'application/json')); - $connectionUrl = "{$this->config['fullUrl']}/gshTemplateExec"; + //Build request logic + $groupCommand = [ + '$memberRequest' => [ + 'actAsSubjectLookup' => [ + 'subjectId' => $userId + ], + 'replaceAllExisting' => 'F', + 'subjectLookups' => [ + ['subjectId' => $userId] + ] + ] + ]; + + $actionEndpoint = "/groups/{$groupName}/members"; 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('PUT', + $actionEndpoint, + json_encode($groupCommand, JSON_THROW_ON_ERROR)); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } - return false; + + return isset($results['WsDeleteMemberResults']['wsGroup']) + && $results['WsDeleteMemberResults']['wsGroup']['name'] == urldecode($groupName); } /** - * ======================== NOT BEING USED ======================== + * Is the user member of the Group * - * For creating/updating Ad-Hoc groups not using the Grouper Template format + * @param string $groupName + * @param string $userId * - * Create or Update a Group where User is Admin/Owner + * @return bool * - * @param array $queryData Array of conditions and data adding new Grouper Group - * @return bool True if added or updated successful - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException */ - public function createUpdateGroup(array $queryData) + public function isMemberOf(string $groupName, string $userId): bool { + if(empty($userId) || empty($groupName)) { + return []; + } - $groupName = htmlentities($queryData['name']); - $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)); - $newGroupName = str_replace(' ', '', $newGroupName); + $groupNameEncoded = $this->urlGrouperEncode($groupName); - $newGroupToSave = "$stemName:$newGroupName"; + $actionEndpoint = "/groups" + . "/{$groupNameEncoded}/members/{$userId}"; + try { + $results = $this->http->sendRequest('GET', $actionEndpoint); + } catch (Exception $e) { + CakeLog::write('error', __METHOD__ . ': An error occurred'); + throw $e; + } - //Build request logic - $groupToSave = array( - "WsRestGroupSaveRequest" => array( - "actAsSubjectLookup" => array( - "subjectId" => $userId - ), - "wsGroupToSaves" => array( - array( - "wsGroup" => array( - "description" => $groupDescription, - "name" => $newGroupToSave, - "displayExtension" => $groupName - ), - "wsGroupLookup" => array( - "groupName" => $groupDescription - ) - ) - ) - ) - ); - $this->http->setHeader(array('Content-Type' => 'application/json', 'Accept' => 'application/json')); - $connectionUrl = "{$this->config['fullUrl']}/groups"; + return isset($results['WsHasMemberLiteResul']['resultMetadata']['resultCode']) + && $results['WsHasMemberLiteResul']['resultMetadata']['resultCode'] === GrouperResultCodesEnum::IS_MEMBER; + } - try { - $results = $this->http->sendRequest('POST', $connectionUrl, json_encode($groupToSave)); + /** + * Removes duplicates where the user is the owner and the updater of the group. Just one line instead of two. + * + * @param array $arrOne + * @param array $arrTwo + * @return array + */ + public function removeDuplicates(array $arrOne, array $arrTwo) + { + //Determine which array is bigger and use as base + $countOne = count($arrOne); + $countTwo = count($arrTwo); + if ($countOne >= $countTwo) { + $arrL = $arrOne; + $arrS = $arrTwo; + } else { + $arrL = $arrTwo; + $arrS = $arrOne; + } - if (isset($results['WsGroupSaveResults']['results']['resultMetadata']['resultCode'])) { - if (stripos($results['WsGroupSaveResults']['results']['resultMetadata']['resultCode'], "Success", 0) !== false) { - return true; + foreach ($arrL as $large) { + foreach ($arrS as $key => $val) { + if ($large['uuid'] == $val['uuid']) { + unset($arrS[$key]); } } - } catch (Exception $e) { - CakeLog::write('error', __METHOD__ . ': An error occurred'); - throw $e; } - return false; + + return array_merge_recursive($arrL, $arrS); } /** - * ======================== NOT BEING USED ======================== - * Used to be used for Grouper group info page. + * Remove a member from a specific Grouper Group * - * Grouper Group information plus a listing of attributes in Grouper for that given Group + * @param array $queryData Array of conditions for querying * - * @param array $queryData Array of conditions for querying - * @return array Record of Grouper attributes for given GroupName - * @throws GrouperLiteException + * @return string Requests success or not + * + * @throws GrouperLiteWidgetException + * @throws JsonException */ - public function getGrouperGroupInfo(array $queryData) + public function removeGrouperGroupMember(array $queryData) { $groupName = $queryData['groupName']; - $groupInfo = array(); + $actionEndpoint = "/groups/$groupName/members"; //Build request logic - $stemToFind = array( - "WsRestGetAttributeAssignmentsRequest" => array( - "attributeAssignType" => "group", - "includeAssignmentsOnAssignments" => "T", - "wsOwnerGroupLookups" => array( - array( - "groupName" => $groupName, - ) - ) - ) - ); - $this->http->setHeader(array('Content-Type' => 'application/json', 'Accept' => 'application/json')); - $connectionUrl = "{$this->config['fullUrl']}/attributeAssignments"; - - try { - $results = $this->http->sendRequest('POST', $connectionUrl, json_encode($stemToFind)); + $userToDelete = [ + 'WsRestDeleteMemberRequest' => [ + 'subjectLookups' => [ + ['subjectId' => $queryData['remUserId']], + ], + 'actAsSubjectLookup' => [ + 'subjectId' => $queryData['userId'] + ] + ] + ]; - //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; + try { + $results = $this->http->sendRequest('POST', + $actionEndpoint, + json_encode($userToDelete, JSON_THROW_ON_ERROR)); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } + + // Parse out relevant records to send front end + return $results['WsDeleteMemberResults']['results'][0]['resultMetadata']['resultCode'] ?? ''; } /** - * ======================== NOT BEING USED ======================== + * Properly encode the attribute for the URL * - * Potential use was for creating adhoc group by a user, not associated to WG. + * @param string $url * - * Gets all Stems/Folders where User is admin/owner + * @return string + */ + public function urlGrouperEncode(string $url) { + $url = urlencode($url); + return str_replace(':', '%3A', $url); + } + + /** + * Used for requests made to Membership endpoint in Grouper WS * * @param array $queryData Array of conditions for querying - * @return array Array of Stems/Folders from Grouper - * @throws GrouperLiteException + * @return array Group records associated to calling method + * @throws GrouperLiteWidgetException + * + * @see getOwnedStems() + * @see getOptinGroups() + * @see getOptOutGroups() + * @see getOwnedGroups() */ - public function getOwnedStems(array $queryData) + private function useMembershipUrl(array $queryData) { - $queryData['groupType'] = 'stemAdmin'; + $groupType = $queryData['groupType']; + $userId = $queryData['userId']; - try { - $results = $this->useMembershipUrl($queryData); + if ($groupType == 'optins' || $groupType == 'optouts') { + $subjectId = 'GrouperAll'; + } elseif ($groupType == 'admin' || $groupType == 'update' || $groupType == 'stemAdmin') { + $subjectId = $userId; + } else { + CakeLog::write('error', __METHOD__ . ": Option of {$groupType} is not supported"); + throw new GrouperLiteWidgetException("Option of {$groupType} is not supported"); + } - if (isset($results['WsGetMembershipsResults']['wsStems']) && $results['WsGetMembershipsResults']['wsStems'] != NULL) { - return $results['WsGetMembershipsResults']['wsStems']; - } + //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['WsRestGetMembershipsRequest']['wsSubjectLookups'][1]['subjectId'] = $userId; + } + + $this->http->setHeader(array('Content-Type' => 'application/json', 'Accept' => 'application/json')); + $actionEndpoint = "/memberships"; + + try { + $results = $this->http->sendRequest('POST', + $actionEndpoint, + json_encode($groupsToShow)); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } - return array(); - } + return $results; + } } diff --git a/Lib/GrouperHTTPWrapper.php b/Lib/GrouperHTTPWrapper.php index dbcd822..8a7725a 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 @@ -35,14 +35,42 @@ */ class GrouperHTTPWrapper extends HttpSocket { - //Connection array sent to HttpSocket - private $_request = array(); + /** + * @var array + * + * Connection array sent to HttpSocket + */ + private array $_request = []; - // Grouper User Account to access Grouper Web Services - private $_user; + /** + * @var string + * + * Grouper User Account to access Grouper Web Services + */ + private string $_user; + + /** + * @var string + * + * Password of Grouper User Account + */ + private string $_password; + + + /** + * @var string + * + * Grouper Servlet + */ + private $_urlServlet = '/grouper-ws/servicesRest/'; + + /** + * @var string + * + * Grouper Service Url + */ + private string $_serviceUrl; - // Password of Grouper User Account - private $_password; /** * GrouperHTTPWrapper constructor. @@ -57,23 +85,38 @@ public function __construct($config = array()) { $this->config['ssl_verify_peer'] = false; } + /** - * Setter for SSL Verify Peer + * Enable SSL Verify configuration * - * @param boolean $val + * @return void */ - public function setSSLVerify(bool $val) { - $this->config['ssl_verify_peer'] = $val; + public function enableSSLVerify(): void + { + $this->config['ssl_verify_peer'] = true; } + /** - * Setter - User account for accessing Grouper Web Services + * Disable SSL Verify Configuration * - * @param string $user + * @return void */ - public function setUser(string $user) { + public function disableSSLVerify(): void + { + $this->config['ssl_verify_peer'] = false; + } - $this->_user = $user; + /** + * Setter - Header settings needed to call Grouper Web Services + * + * @param array $headerSetting + */ + public function setHeader(array $headerSetting): void + { + if (!empty($headerSetting)) { + $this->_request['header'] = $headerSetting; + } } /** @@ -81,18 +124,44 @@ public function setUser(string $user) { * * @param string $password */ - public function setPassword(string $password) { + public function setPassword(string $password): void + { + if (!empty($password)) { + $this->_password = $password; + } + } - $this->_password = $password; + /** + * @param string $domain + * @param string $version + * + * @return void + */ + public function setServiceUrl(string $domain, string $version): void + { + if(!empty($domain) && !empty($version)) { + $this->_serviceUrl = $domain . $this->_urlServlet . $version; + } } /** - * Setter - Header settings needed to call Grouper Web Services + * @return string + */ + public function getServiceUrl(): string + { + return $this->_serviceUrl; + } + + /** + * Setter - User account for accessing Grouper Web Services * - * @param array $headerSetting + * @param string $user */ - public function setHeader(array $headerSetting) { - $this->_request['header'] = $headerSetting; + public function setUser(string $user): void + { + if (!empty($user)) { + $this->_user = $user; + } } /** @@ -102,21 +171,22 @@ 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 { - + public function sendRequest(string $method, string $endPoint, string $body = ''): array { + $uri = "{$this->_serviceUrl}{$endPoint}"; $this->_request['method'] = $method; $this->_request['uri'] = $this->_parseUri($uri); $this->_request['uri']['user'] = $this->_user; $this->_request['uri']['pass'] = $this->_password; $this->_request['body'] = $body; + CakeLog::write('debug', __METHOD__ . '::connection url: ' . $uri); try { $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 @@ -124,6 +194,15 @@ public function sendRequest(string $method, string $uri, string $body = ''): arr CakeLog::write('error', __METHOD__ . ': Grouper WS returned non-200 of ' . var_export($apiResults->body(), true)); } - return json_decode($apiResults->body(), true); + $payload = json_decode($apiResults->body(), true); + + CakeLog::write('debug', __METHOD__ . '::payload: ' . var_export($payload, true)); + + $successHeader = $apiResults->getHeader('X-Grouper-success'); + if (empty($successHeader)) { + throw new RuntimeException('Web service did not even respond!'); + } + + return $payload; } } 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..f9a2cf7 --- /dev/null +++ b/Lib/enum.php @@ -0,0 +1,26 @@ + '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'); +} + +class GrouperSpecialGroups { + /** Group whose members can create Groups via Template process */ + const TEMPLATE_CREATION_GROUP = 'ref:workinggroupadmins'; + + /** Group whose members cannot see Grouper button, even if admin */ + const GROUPER_VISIBLE_GROUP = 'app:comanage:LiteUI:grouperVisible'; +} + +class GrouperResultCodesEnum { + const IS_MEMBER = 'IS_MEMBER'; + const SUCCESS = 'SUCCESS'; + const GROUP_NOT_FOUND = 'GROUP_NOT_FOUND'; + const NO_SUCH_OBJECT = 'NO_SUCH_OBJECT'; + const SUCCESS_NO_INFO = 'SUCCESS_NO_INFO'; + const EXECUTE_FAILED = 'EXECUTE_FAILED'; +} \ No newline at end of file diff --git a/Lib/lang.php b/Lib/lang.php index bcae1dd..34bad66 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,10 @@ '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.config.act-as-grp-name' => 'Act-As Group Name', + 'pl.grouperlite.config.act-as-grp-name.desc' => 'Members of the Group are authorized to Act-As another User.', 'pl.grouperlite.crumb.root' => 'Grouper', 'pl.grouperlite.nav.groups-can-join' => 'Groups I can join', @@ -28,6 +32,7 @@ 'pl.grouperlite.nav.create-email' => 'Create email list', 'pl.grouperlite.nav.emaillists' => 'Email Lists', 'pl.grouperlite.nav.groups' => 'Groups', + 'pl.grouperlite.nav.users-presided' => 'Users I manage', 'pl.grouperlite.dashboard.heading.groups' => 'Groups', 'pl.grouperlite.dashboard.heading.email-lists' => 'Email lists', @@ -166,5 +171,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..5181291 --- /dev/null +++ b/Model/CoGrouperLiteWidget.php @@ -0,0 +1,159 @@ + 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 + ) + ), + // TODO: Are there any limitation regarding the acceptable group name characters + // For COmanage there are so we need to check this as well + 'act_as_grp_name' => array( + 'rule' => '/.*/', + 'required' => false, + 'allowEmpty' => true + ) + ); + + /** + * 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..18d776e 100644 --- a/Model/GrouperGroup.php +++ b/Model/GrouperGroup.php @@ -25,24 +25,24 @@ * @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 { + // XXX According to the documentation (https://spaces.at.internet2.edu/display/Grouper/UI+Terminology) + // the displayExtension is the Friendly Name of the Group. /** @var string $name used by CakePHP for locating model */ - public $name = "GrouperGroup"; + 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'; @@ -65,7 +65,7 @@ class GrouperGroup extends GrouperLiteAppModel 'ref:internet2-collab' ); - //Stem for email groups, only email groups using this stem are viewable + // Stem for email groups, only email groups using this stem are viewable private $emailStem = 'app:sympa'; @@ -75,36 +75,26 @@ class GrouperGroup extends GrouperLiteAppModel * * @param string $userId Id of User * @return String T or F - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException * * @see GrouperGroup::resetUserOwner() * + * @since COmanage Registry v4.4.0 */ - 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); + $args = array(); + $args['userId'] = $userId; try { - $args = array(); - $args['userId'] = $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'; - } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } + + return count($ownGroups) > 0 ? 'T' : 'F'; } /** @@ -112,60 +102,34 @@ public function isUserOwner(string $userId) * * @param string $userId Id of User * @return String T or F - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException * + * @since COmanage Registry v4.4.0 */ - 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(); - $args['userId'] = $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) { - if ($memberOfGroup['name'] == $this->grouperVisibleGroup) { - $member = 'T'; - break; - } - } - - if ($member == 'T') { - CakeSession::write('Plugin.Grouper.isGrouperVisible', 'T'); - return 'T'; - } else { - CakeSession::write('Plugin.Grouper.isGrouperVisible', 'F'); - return 'F'; - } - + $isMember = $this->grouperAPI->isMemberOf(GrouperSpecialGroups::GROUPER_VISIBLE_GROUP, $userId); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } + + return $isMember ? 'T' : 'F'; } /** - * Used to instantiate API class + * Grouper API Instance Singleton + * + * @throws GrouperLiteWidgetException * - * Tried to use the constructor for GrouperGroup to set this but it was triggering the GrouperAPIAccess class - * to instantiate before the Controller was able populate the connection settings from the DB into the Session, - * so error was being thrown. - * Now will call this before each function call to verify set. + * @since COmanage Registry v4.4.0 */ - private function initApi() - { - if ($this->grouperAPI == null) { - $this->grouperAPI = new GrouperApiAccess(); + private function initApi(array $cfg) { + if ($this->grouperAPI === null) { + $this->grouperAPI = new GrouperApiAccess($cfg); } } @@ -175,36 +139,27 @@ 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 * + * @since COmanage Registry v4.4.0 */ - public function filteredMemberOfGroups(array $conditions) + public function filteredMemberOfGroups(array $conditions, array $cfg) { - $this->initApi(); + $this->initApi($cfg); try { - - $memberOfGroups = $this->memberOfGroups($conditions); + $memberOfGroups = $this->memberOfGroups($conditions, $cfg); $conditions['groupType'] = 'optouts'; // Determine which groups can be left by user, if want. $optOutGroups = $this->grouperAPI->getOptionalGroups($conditions); + $optOutGroupsNames = Hash::combine($optOutGroups, '{n}.name', '{n}.displayExtension'); foreach ($memberOfGroups as &$memberOfGroup) { - $memberOfGroup['optOut'] = false; - foreach ($optOutGroups as $key => $value) { - if ($value['name'] == $memberOfGroup['name']) { - //Match! - $memberOfGroup['optOut'] = true; - //Remove Optin group since already found and now less loops - unset($optOutGroups[$key]); - break; - } - } + $memberOfGroup['optOut'] = isset($optOutGroupsNames[$memberOfGroup['name']]); } - $groupResults = array_values($memberOfGroups); - return $this->getFriendlyWorkingGroupName($groupResults, 'member'); + return $this->getFriendlyWorkingGroupName($memberOfGroups, 'member'); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); @@ -217,15 +172,16 @@ 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 * + * @since COmanage Registry v4.4.0 */ - private function memberOfGroups(array $conditions) + private function memberOfGroups(array $conditions, array $cfg) { + $this->initApi($cfg); try { - return $this->grouperAPI->getGrouperMemberOfGroups($conditions); - + return $this->grouperAPI->getGrouperMemberOfGroups($conditions['userId']); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; @@ -238,22 +194,19 @@ 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 * + * @since COmanage Registry v4.4.0 */ - public function leaveGroup(string $userId, string $groupName) + public function leaveGroup(string $userId, string $groupName, array $cfg) { - $this->initApi(); + $this->initApi($cfg); try { - $args = array(); - $args['LeaveJoin'] = 'Leave'; - $args['userId'] = $userId; - $args['groupName'] = $groupName; - - return $this->grouperAPI->grouperGroupLeaveOrJoin($args); - + return $this->grouperAPI->leaveGrouperGroup($userId, $groupName); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; @@ -266,21 +219,16 @@ 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 * + * @since COmanage Registry v4.4.0 */ - public function joinGroup(string $userId, string $groupName) + public function joinGroup(string $userId, string $groupName, array $cfg) { - $this->initApi(); + $this->initApi($cfg); try { - $args = array(); - $args['LeaveJoin'] = 'Join'; - $args['userId'] = $userId; - $args['groupName'] = $groupName; - - return $this->grouperAPI->grouperGroupLeaveOrJoin($args); - + return $this->grouperAPI->joinGrouperGroup($userId, $groupName); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; @@ -292,17 +240,16 @@ 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 * + * @since COmanage Registry v4.4.0 */ - public function ownerGroups(array $conditions) + public function ownerGroups(array $conditions, array $cfg) { - $this->initApi(); + $this->initApi($cfg); try { - $resultSet = $this->grouperAPI->getOwnedGroups($conditions); - return $this->getFriendlyName(array_values($resultSet)); - + return $this->grouperAPI->getOwnedGroups($conditions); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; @@ -317,35 +264,35 @@ 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 * + * @since COmanage Registry v4.4.0 */ - public function membersInGroup(array $conditions, string $userId) + public function membersInGroup(array $conditions, string $userId, array $cfg) { - $this->initApi(); + $this->initApi($cfg); $conditions['userId'] = $userId; try { $groupMembers = $this->grouperAPI->getMembersInGroup($conditions); + } catch (Exception $e) { + CakeLog::write('error', __METHOD__ . ': An error occurred'); + throw $e; + } - if (count($groupMembers) < 1) { - return $groupMembers; - } - - $finalMembers = array(); - foreach ($groupMembers as $member) { - if ($member['sourceId'] !== 'g:gsa') { - $finalMembers[] = $member; - } + if (count($groupMembers) < 1) { + return $groupMembers; + } + $finalMembers = []; + foreach ($groupMembers as $member) { + if ($member['sourceId'] !== 'g:gsa') { + $finalMembers[] = $member; } - return $finalMembers; - } catch (Exception $e) { - CakeLog::write('error', __METHOD__ . ': An error occurred'); - throw $e; } + return $finalMembers; } /** @@ -354,20 +301,18 @@ 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 * + * @since COmanage Registry v4.4.0 */ - public function addMemberToGroup(array $conditions, string $userId) + public function addGrouperGroupMember(array $conditions, string $userId, array $cfg) { - $this->initApi(); + $this->initApi($cfg); $conditions['userId'] = $userId; try { - $resultAdd = $this->grouperAPI->addMemberToGroup($conditions); - - return $resultAdd; - + return $this->grouperAPI->addGrouperGroupMember($conditions); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; @@ -380,20 +325,18 @@ 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 * + * @since COmanage Registry v4.4.0 */ - public function removeMemberToGroup(array $conditions, string $userId) + public function removeGrouperGroupMember(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->removeGrouperGroupMember($conditions); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; @@ -408,32 +351,24 @@ 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 + * + * @since COmanage Registry v4.4.0 */ - public function optinGroups(array $conditions) + public function optinGroups(array $conditions, array $cfg) { - $this->initApi(); + $this->initApi($cfg); + $conditions['groupType'] = 'optins'; try { - $conditions['groupType'] = 'optins'; - + // Groups the user can join or leave $joinOrLeave = $this->grouperAPI->getOptionalGroups($conditions); - $userGroups = $this->memberOfGroups($conditions); - - //See if Optin group match any of the groups user already belongs to. - foreach ($joinOrLeave as $key => $value) { - foreach ($userGroups as $userGroup) { - if ($value['name'] == $userGroup['name']) { - //Match!! - //Removing from array since already a member of. - unset($joinOrLeave[$key]); - break; - } - } - } - - return $this->getFriendlyName(array_values($joinOrLeave)); - + // Groups the user is a member of + $userGroups = $this->memberOfGroups($conditions, $cfg); + // 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)); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; @@ -445,43 +380,22 @@ public function optinGroups(array $conditions) * * @param string $userId Id of User * @return string T for True and F for False - * @throws GrouperLiteException + * @throws GrouperLiteWidgetException + * + * @since COmanage Registry v4.4.0 */ - 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(); - $args['userId'] = $userId; - - $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) { - if ($memberOfGroup['name'] == $this->templateCreationGroup) { - $member = 'T'; - break; - } - } - - if ($member == 'T') { - CakeSession::write('Plugin.Grouper.isTemplateUser', 'T'); - return 'T'; - } - CakeSession::write('Plugin.Grouper.isTemplateUser', 'F'); - return 'F'; - + $isMember = $this->grouperAPI->isMemberOf(GrouperSpecialGroups::TEMPLATE_CREATION_GROUP, $userId); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } + return $isMember ? 'T' : 'F'; } /** @@ -490,12 +404,13 @@ 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 * + * @since COmanage Registry v4.4.0 */ - 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 +442,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. * @@ -550,10 +452,12 @@ private function resetUserOwner() * @param array $conditions Listing of conditions for display of records, including UserId * @return array Records that meet search criteria * @throws Exception Captured in Controller + + * @since COmanage Registry v4.4.0 */ - 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 @@ -589,7 +493,7 @@ public function getSearchedGroups(array $conditions) } /** - * Return array of Working Groups for display on coManage site. + * Return array of Working Groups for display on COManage site. * Logic is for each WG to have one key=>value of main WG name, then array of all associated * Groups. * @@ -598,13 +502,16 @@ public function getSearchedGroups(array $conditions) * * @param array $groups Listing of Groups * @return array Listing of Groups in WG format for display - * + + * @since COmanage Registry v4.4.0 */ - private function getFriendlyWorkingGroupName(array $groups, $method) - { + private function getFriendlyWorkingGroupName(array $groups, string $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) { @@ -674,10 +581,6 @@ private function getFriendlyWorkingGroupName(array $groups, $method) } $tempGroup['WGApp'] = $appName; if ($method == 'member') { - // Screening out groups owned since this page show memberships not ownership! -// if ($tempGroup['WGRole'] !== 'admin' && $tempGroup['WGRole'] !== 'owner' && !$mainGroup ) { -// $workingGroups[] = $tempGroup; -// } if(!$mainGroup) { $workingGroups[] = $tempGroup; unset($groups[$arrayIndex]); @@ -719,7 +622,7 @@ private function getFriendlyWorkingGroupName(array $groups, $method) } } - $friendlyGroups = $this->getFriendlyName(array_values($groups)); + $friendlyGroups = array_values($groups); //Now need to add the groups back together for one set foreach ($friendlyGroups as $friendlyGroup) { @@ -728,26 +631,4 @@ private function getFriendlyWorkingGroupName(array $groups, $method) return $finalWorkingGroups; } - - - /** - * Determine if result set contains friendly name, if so add as a new attribute in result set - * NOTE: Used by Ad-Hoc groups only, since WG are listed under a Grouping - * - * @param array $groups Current result set of Groups - * @return array Same result set with added param for friendly name - * - */ - private function getFriendlyName(array $groups) - { - - //According to Jira ICPCO-200, will only display the last section of the stem in UI and then hover over for rest. - foreach ($groups as &$group) { - $friendlySections = explode(':', $group['displayName']); - $group['friendlyName'] = end($friendlySections); - } - - return $groups; - } - } 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/README.md b/README.md index 80d5219..11242ca 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,14 @@ -# coManage Grouper +# COmanage Grouper Lite Widget Plugin ## Instructions: -### To use in coMananage: -* With your favorite command line tool, navigate to a directory you wish to use to house the coManage plugin. -* Copy the source files from the GrouperLite Plugin located at [https://github.internet2.edu/internet2/comanage-grouper-widget](https://github.internet2.edu/internet2/comanage-grouper-widget) +### To use in COManage: +* With your favorite command line tool, navigate to a directory you wish to use to house the COmanage plugin. +* Copy the source files from the GrouperLiteWidget Plugin located at [https://github.internet2.edu/internet2/comanage-grouper-widget](https://github.internet2.edu/internet2/comanage-grouper-widget) * You can either download the source files as a ZipFile or Clone the directory from GitHub repository. - * ZipFile: unzip into a new ```GrouperLite/``` directory. - * Clone: ```git clone https://github.internet2.edu/internet2/comanage-grouper-widget GrouperLite/``` + * ZipFile: unzip into a new `GrouperLiteWidget/` directory. + * Clone: `git clone https://github.internet2.edu/internet2/comanage-grouper-widget GrouperLiteWidget/` * Copy the application location for symlinking below. -* Navigate to your coManage source directory. -* From the root of your coManage installation, go to the ```local/Plugin/``` directory. -* Symlink your GrouperLite location under the Plugin directory. - * ```ln -s /GrouperLite /local/Plugin/GrouperLite``` - - +* Navigate to your COmanage source directory. +* From the root of your COmanage installation, go to the `local/Plugin/` directory. +* Symlink your GrouperLiteWidget location under the Plugin directory. + * `ln -s /GrouperLiteWidget /local/Plugin/GrouperLiteWidget` diff --git a/Test/HttpRequests/grouper.http b/Test/HttpRequests/grouper.http new file mode 100644 index 0000000..4fb3c28 --- /dev/null +++ b/Test/HttpRequests/grouper.http @@ -0,0 +1,16 @@ +# https://spaces.at.internet2.edu/display/Grouper/Grouper+Web+Services + +### GET groups the actAs is a member of +GET https://{{ host }}/grouper-ws/servicesRest/{{version}}/subjects/{{identifier}}/groups? + wsLiteObjectType=WsRestGetGroupsLiteRequest& + actAsSubjectId={{identifier}} +Authorization: Basic {{username}} {{password}} + + +### isMember of Group +GET https://{{ host }}/grouper-ws/servicesRest/{{version}}/groups/test%3AviewOnly/members/{{identifier}} +Authorization: Basic {{username}} {{password}} + +### Get memberships +GET https://{{ host }}/grouper-ws/servicesRest/{{version}}/subjects/{{identifier}}/groups/test%3AviewOnly/memberships +Authorization: Basic {{username}} {{password}} \ No newline at end of file diff --git a/Test/HttpRequests/http-client.env.json b/Test/HttpRequests/http-client.env.json new file mode 100644 index 0000000..9313194 --- /dev/null +++ b/Test/HttpRequests/http-client.env.json @@ -0,0 +1,6 @@ +{ + "grouper": { + "host": "grouper.dev.at.internet2.edu", + "version": "v2_5_000" + } +} \ No newline at end of file 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(); ?>