diff --git a/Controller/GrouperGroupsController.php b/Controller/GrouperGroupsController.php index 829e0f0..1208bcf 100644 --- a/Controller/GrouperGroupsController.php +++ b/Controller/GrouperGroupsController.php @@ -42,6 +42,7 @@ class GrouperGroupsController extends GrouperLiteWidgetAppController // Dynamic properties are deprecated, so we will define the property here private $userId = null; + private $actAsIdentifier = null; public $uses = [ 'GrouperLiteWidget.GrouperGroup', @@ -83,7 +84,7 @@ public function addSubscriber(): void : $groupName; try { - if(!$this->GrouperGroup->addGroupMember($this->userId, + if(!$this->GrouperGroup->addGroupMember($this->getActAsIdentifier(), $groupNameFormatted, $addUserId, $this->CoGrouperLiteWidget->getConfig())) { @@ -132,11 +133,35 @@ public function beforeFilter() $cfg = $this->CoGrouperLiteWidget->find('first', $args); // Set the config so that everybody can access it $this->CoGrouperLiteWidget->setConfig($cfg); + + // XXX We will make the ActAs calculations here the beforeRender callback does not always fire + // This is happening because we have a mixed schema with API calls as well as MVC structure. + // Get the ActAs User Data + $roles = $this->Role->calculateCMRoles(); + $co_person_id = $roles['copersonid']; + // Get the act as data from the database + $args = []; + $args['conditions']['ActAsPerson.co_person_id'] = $co_person_id; + $args['contain'] = false; + $act_as_record = $this->ActAsPerson->find('first', $args); + $this->set('vv_act_as_people', []); + + // Get ActAs configuration + if(!empty($act_as_record)) { + $act_as_person = $this->GrouperGroup->dataConstructForPicker($this->cur_co['Co']['id'], + PeoplePickerModeEnum::All, + [$act_as_record['ActAsPerson']['act_as_co_person_id']]); + $this->set('vv_act_as_people', $act_as_person); + $this->set('vv_act_as_record_id', $act_as_record['ActAsPerson']['id']); + $act_as_identifier = $this->GrouperGroup->getIdentifierFromPersonId($act_as_record['ActAsPerson']['act_as_co_person_id'], + $cfg['CoGrouperLiteWidget']['identifier_type']); + $this->setActAsIdentifier($act_as_identifier); + } } /** * Callback after controller methods are invoked but before views are rendered. - * - precondition: Request Handler component has set $this->request + * - Precondition: A request Handler component has set $this->request * * @since COmanage Registry v4.4.0 */ @@ -147,38 +172,29 @@ public function beforeRender() { $this->set('vv_config', $cfg); $this->set('vv_title', _txt('pl.grouperlite.title.dashboard')); - $this->set('vv_is_user_owner', $this->GrouperGroup->isUserGroupOwner($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']); $roles = $this->Role->calculateCMRoles(); $this->set('vv_copersonid', $roles['copersonid'] ?? null); $this->set('vv_picker_mode', PeoplePickerModeEnum::All); - // Get the ActAs User Data - $co_person_id = $this->viewVars['roles']['copersonid']; - // Get the act as data from the database - $args = []; - $args['conditions']['ActAsPerson.co_person_id'] = $co_person_id; - $args['contain'] = false; - $act_as_record = $this->ActAsPerson->find('first', $args); - $this->set('vv_act_as_people', []); - if(!empty($act_as_record)) { - $act_as_person = $this->GrouperGroup->dataConstructForPicker($this->cur_co['Co']['id'], - PeoplePickerModeEnum::All, - [$act_as_record['ActAsPerson']['act_as_co_person_id']]); - $this->set('vv_act_as_people', $act_as_person); - $this->set('vv_act_as_record_id', $act_as_record['ActAsPerson']['id']); - } + $this->set('vv_act_as_identifier', $this->getActAsIdentifier()); + $this->set('vv_is_user_owner', + $this->GrouperGroup->isUserGroupOwner($this->getUserId(), + $this->getActAsIdentifier(), + $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)); } /** - * @return null + * @return null|string */ - public function getUserId() + public function getActAsIdentifier(): ?string { - return $this->userId; + //If the actor Identifier is not set we will return the current user + return $this->actAsIdentifier ?? $this->getUserId(); } /** @@ -208,6 +224,14 @@ public function findSubscriber(): void $this->set('_serialize', 'matches'); } + /** + * @return null|string + */ + public function getUserId(): null|string + { + return $this->userId; + } + /** * GroupMember vue route for rendering groupmemberapi results * @@ -254,7 +278,7 @@ public function groupSubscribers(): void : $groupName; try { - $subscribers = $this->GrouperGroup->getGroupMembers($this->userId, + $subscribers = $this->GrouperGroup->getGroupMembers($this->getActAsIdentifier(), $groupNameFormatted, $this->CoGrouperLiteWidget->getConfig()); } catch (Exception $e) { @@ -273,6 +297,7 @@ public function groupOwnerApi(): void { //Set initial setting $arguments = [ 'userId' => $this->userId, + 'actorUserId' => $this->getActAsIdentifier(), 'cfg' => $this->CoGrouperLiteWidget->getConfig() ]; @@ -282,7 +307,7 @@ public function groupOwnerApi(): void { //Add settings for search Owned Groups $arguments['searchCriteria'] = $searchCriteria; - $arguments['searchPage'] = 'ownerGroups'; + $arguments['searchPage'] = 'getOwnedGroups'; $func = 'getSearchedGroups'; $errorHint = 'Search'; @@ -314,6 +339,7 @@ public function groupMemberApi(): void { //Set initial setting $arguments = [ 'userId' => $this->userId, + 'actorUserId' => $this->getActAsIdentifier(), 'cfg' => $this->CoGrouperLiteWidget->getConfig() ]; @@ -358,6 +384,7 @@ public function groupOptinApi() { //Set initial setting $arguments = [ 'userId' => $this->userId, + 'actorUserId' => $this->getActAsIdentifier(), 'cfg' => $this->CoGrouperLiteWidget->getConfig() ]; @@ -401,7 +428,7 @@ public function groupCreateTemplate() { if ($this->request->is('post')) { try { - $status = $this->GrouperGroup->createGroupWithTemplate($this->userId, + $status = $this->GrouperGroup->createGroupWithTemplate($this->getActAsIdentifier(), $this->request->data, $this->CoGrouperLiteWidget->getConfig()); @@ -447,19 +474,9 @@ public function isAuthorized(): array|bool $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'] = !empty($roles['copersonid']) ? $roles['copersonid'] : $pids['copersonid']; - $args['contain'] = false; - - $identifiers = $this->Identifier->find('first', $args); - if(!empty($identifiers) - && is_array($identifiers) - && isset($identifiers['Identifier']['identifier']) - ) { - $this->setUserId($identifiers['Identifier']['identifier']); - } + $copersonid = !empty($roles['copersonid']) ? $roles['copersonid'] : $pids['copersonid']; + $this->setUserId($this->GrouperGroup->getIdentifierFromPersonId($copersonid, + $cfg['CoGrouperLiteWidget']['identifier_type'])); // Find if the user belongs to Group $eligibleGroup = $cfg['CoGrouperLiteWidget']['act_as_grp_name']; @@ -515,7 +532,7 @@ public function joinGroup(): void try { // Add myself - if(!$this->GrouperGroup->addGroupMember($this->userId, + if(!$this->GrouperGroup->addGroupMember($this->getActAsIdentifier(), $groupName, $this->userId, $this->CoGrouperLiteWidget->getConfig())) { @@ -542,7 +559,7 @@ public function leaveGroup(): void $groupName = urldecode($this->request->query['GroupName']); try { - if(!$this->GrouperGroup->removeGroupMember($this->userId, + if(!$this->GrouperGroup->removeGroupMember($this->getActAsIdentifier(), $groupName, $this->userId, $this->CoGrouperLiteWidget->getConfig())) { @@ -601,7 +618,7 @@ public function removeSubscriber(): void : $groupName; try { - if(!$this->GrouperGroup->removeGroupMember($this->userId, + if(!$this->GrouperGroup->removeGroupMember($this->getActAsIdentifier(), $groupNameFormatted, $remUserId, $this->CoGrouperLiteWidget->getConfig())) { @@ -645,9 +662,17 @@ public function restResponse(int $status, } /** - * @param null $userId + * @param null|string $actAsIdentifier + */ + public function setActAsIdentifier(?string $actAsIdentifier): void + { + $this->actAsIdentifier = $actAsIdentifier; + } + + /** + * @param null|string $userId */ - private function setUserId($userId): void + private function setUserId(?string $userId): void { $this->userId = $userId; } @@ -677,7 +702,7 @@ public function userManagerApi(): void $memberId = urldecode($this->request->query['memberid']); try { - $groupsimanage = $this->GrouperGroup->getManagedUsers($this->userId, + $groupsimanage = $this->GrouperGroup->getManagedUsers($this->getActAsIdentifier(), $memberId, $cfg); } catch (Exception $e) { @@ -694,10 +719,10 @@ public function userManagerApi(): void } /** - * Override the default sanity check performed in AppController + * Override the default check performed in AppController * * @since COmanage Registry v4.3.0 - * @return Boolean True if sanity check is successful + * @return Boolean True if check is successful */ public function verifyRequestedId(): bool diff --git a/Lib/GrouperApiAccess.php b/Lib/GrouperApiAccess.php index 6f77fa9..5bed1e0 100644 --- a/Lib/GrouperApiAccess.php +++ b/Lib/GrouperApiAccess.php @@ -472,11 +472,12 @@ public function getUserMemberships(string $userId, string $actAsUserId, string $ true); // Build request logic + // XXX This is not working very well? $data = [ 'WsRestGetMembershipsRequest' => [ 'fieldName' => $groupType, 'actAsSubjectLookup' => [ - 'subjectId' => true ? '': $actAsUserId + 'subjectId' => ($userId !== $actAsUserId) ? $actAsUserId : '' ], 'wsSubjectLookups' => [ ['subjectId' => $isOptinsOrOptouts ? GrouperConfigEnums::ALL : $userId] diff --git a/Model/GrouperGroup.php b/Model/GrouperGroup.php index ad83576..fd4184e 100644 --- a/Model/GrouperGroup.php +++ b/Model/GrouperGroup.php @@ -61,26 +61,28 @@ class GrouperGroup extends GrouperLiteWidgetAppModel /** - * Verifies if user is an owner/admin of a group. + * Verifies if a user is an owner/admin of a group. * Session variable is reset on Group Creation and Group Deletion * - * @param string $userId Id of User - * @return String T or F - * @throws GrouperLiteWidgetException + * @param string $userId Id of User + * @param string $actorUserId + * @param array $cfg * + * @return bool T or F + * @throws GrouperLiteWidgetException * @since COmanage Registry v4.4.0 */ - public function isUserGroupOwner(string $userId, array $cfg): bool + public function isUserGroupOwner(string $userId, string $actorUserId, array $cfg): bool { $this->initApi($cfg); - if(empty($userId)) { + if(empty($userId) || empty($actorUserId)) { return false; } try { - $resultsAdmin = $this->grouperAPI->getUserMemberships($userId, $userId, GrouperGroupTypeEnum::ADMIN); - $resultsUpdate = $this->grouperAPI->getUserMemberships($userId, $userId, GrouperGroupTypeEnum::UPDATE); + $resultsAdmin = $this->grouperAPI->getUserMemberships($userId, $actorUserId, GrouperGroupTypeEnum::ADMIN); + $resultsUpdate = $this->grouperAPI->getUserMemberships($userId, $actorUserId, GrouperGroupTypeEnum::UPDATE); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; @@ -127,73 +129,86 @@ private function initApi(array $cfg) { } /** - * Return all Groups that a User belongs to in Grouper. - * Will also add OptOut Groups and flag them as joined so can display Optout option in UI. + * Add a member to a specific Grouper Group * - * @param string $userId + * @param string $actorUserId Id of User + * @param string $groupName + * @param string $addUserId * @param array $cfg * - * @return array Records of Groups from Grouper that the User belongs to - * @throws GrouperLiteWidgetException + * @return string success of Request + * @throws GrouperLiteWidgetException Captured in Controller + * @throws JsonException * @since COmanage Registry v4.4.0 */ - public function filteredMemberOfGroups(string $userId, array $cfg): array + public function addGroupMember(string $actorUserId, string $groupName, string $addUserId, array $cfg) { $this->initApi($cfg); - try { - $memberOfGroups = $this->memberOfGroups($userId, $userId, $cfg); - // Determine which groups can be left by user, if wanted. - $optOutGroups = $this->grouperAPI->getUserMemberships($userId, $userId, GrouperGroupTypeEnum::OPTOUTS); - $optOutGroupsNames = Hash::combine($optOutGroups, '{n}.name', '{n}.displayExtension'); + return $this->grouperAPI->addGroupMember($actorUserId, $groupName, $addUserId); + } - foreach ($memberOfGroups as &$memberOfGroup) { - $memberOfGroup['optOut'] = isset($optOutGroupsNames[$memberOfGroup['name']]); + /** + * Breakout Working Groups from AdHoc Groups. + * + * @param array $recordSet + * @return array[] + * + */ + public function breakoutWrkFromAdHocGroups(array $recordSet): array + { + $wgData = []; + $notWGData = []; + //Parse out the Working Groups from the Ad-hoc groups + foreach ($recordSet as $record) { + if (isset($record['WGName'])) { + $wgData[] = $record; + } else { + $notWGData[] = $record; } - - return $this->getFriendlyWorkingGroupName($memberOfGroups, 'member'); - - } catch (Exception $e) { - CakeLog::write('error', __METHOD__ . ': An error occurred'); - throw $e; } - } + return [ + 'adhoc' => $notWGData, + 'working' => $wgData + ]; + } /** - * Find People based on mode and term + * Create a new Grouper Group using the Template methodology in Grouper * - * @param integer $coId CO ID - * @param string $mode Search mode to apply filters for - * @param string|null $term Search block + * @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 GrouperLiteWidgetException * - * @return array Array of CO Person records * @since COmanage Registry v4.4.0 */ - public function findForPicker(int $coId, string $mode, ?string $term): array + public function createGroupWithTemplate(string $userId, array $groupData, array $cfg) { - $coPersonIds = []; - $this->CoPerson = ClassRegistry::init('CoPerson'); - - // jquery Autocomplete sends the search as url?term=foo - if(!empty($term)) { - // Leverage-model-specific keyword search - - // Note EmailAddress and Identifier don't support substring search - foreach(array('Name', 'EmailAddress', 'Identifier') as $m) { - $hits = $this->CoPerson->$m->search($coId, $term, 25); - - $coPersonIds = array_merge($coPersonIds, Hash::extract($hits, '{n}.CoPerson.id')); - } + $this->initApi($cfg); + //Need to massage incoming data to meet Grouper Template requirements + $fields = array( + 'gsh_input_isSympa', + 'gsh_input_isSympaModerated', + 'gsh_input_isOptin', + 'gsh_input_isConfluence', + 'gsh_input_isJira' + ); + // Template does not except true/false, so convert to string and send that way + foreach ($fields as $field) { + ($groupData[$field] == '0') ? $groupData[$field] = 'false' : $groupData[$field] = 'true'; } - $coPersonIds = array_unique($coPersonIds); - - // Look up additional information to provide hints as to which person is which. - // We only do this when there are relatively small numbers of results to - // avoid making a bunch of database queries early in the search. - - return $this->dataConstructForPicker($coId, $term,$coPersonIds); + $args = array(); + $args['userId'] = $userId; + $args['data'] = $groupData; + try { + return $this->grouperAPI->createGroupWithTemplate($args); + } catch (Exception $e) { + CakeLog::write('error', __METHOD__ . ': An error occurred'); + throw $e; + } } /** @@ -293,338 +308,386 @@ public function dataConstructForPicker(int $coId, string $mode, array $coPersonI } /** - * Internal process used by other functions to fetch Groups the User is a member of + * Return all Groups that a User belongs to in Grouper. + * Will also add OptOut Groups and flag them as joined so can display an Optout option in the UI. * - * @param string $actorUserId * @param string $userId + * @param string $actorUserId * @param array $cfg * * @return array Records of Groups from Grouper that the User belongs to * @throws GrouperLiteWidgetException * @since COmanage Registry v4.4.0 */ - private function memberOfGroups(string $actorUserId, string $userId, array $cfg) + public function filteredMemberOfGroups(string $userId, string $actorUserId, array $cfg): array { $this->initApi($cfg); try { - return $this->grouperAPI->getUserGroups($actorUserId, $userId); - } catch (Exception $e) { - CakeLog::write('error', __METHOD__ . ': An error occurred'); - throw $e; - } - } + $memberOfGroups = $this->memberOfGroups($actorUserId, $userId, $cfg); + // Determine which groups can be left by user, if wanted. + $optOutGroups = $this->grouperAPI->getUserMemberships($userId, $actorUserId, GrouperGroupTypeEnum::OPTOUTS); + $optOutGroupsNames = Hash::combine($optOutGroups, '{n}.name', '{n}.displayExtension'); - /** - * Return all Grouper Groups that the User has a role of owner/admin - * - * @param string $userId - * @param array $cfg - * - * @return array - * @throws GrouperLiteWidgetException - * @since COmanage Registry v4.4.0 - */ - public function getOwnedGroups(string $userId, array $cfg): array - { - if(empty($userId)) { - return false; - } + foreach ($memberOfGroups as &$memberOfGroup) { + $memberOfGroup['optOut'] = isset($optOutGroupsNames[$memberOfGroup['name']]); + } - $this->initApi($cfg); + return $this->getFriendlyWorkingGroupName($memberOfGroups, 'member'); - try { - $resultsAdmin = $this->grouperAPI->getUserMemberships($userId, $userId, GrouperGroupTypeEnum::ADMIN); - $resultsUpdate = $this->grouperAPI->getUserMemberships($userId, $userId, GrouperGroupTypeEnum::UPDATE); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } - - return $this->removeDuplicates($resultsAdmin, $resultsUpdate); } /** - * Return all Grouper Groups that - * - the User(me) has a role of owner/admin - * - the User(member User) is a member + * Find People based on mode and term * - * @param string $managerId - * @param string $memberId - * @param array $cfg + * @param integer $coId CO ID + * @param string $mode Search mode to apply filters for + * @param string|null $term Search block * - * @return array - * @throws GrouperLiteWidgetException + * @return array Array of CO Person records * @since COmanage Registry v4.4.0 */ - public function getManagedUsers(string $managerId, string $memberId, array $cfg): array { - if(empty($managerId) || empty($memberId)) { - return false; - } + public function findForPicker(int $coId, string $mode, ?string $term): array + { + $coPersonIds = []; + $this->CoPerson = ClassRegistry::init('CoPerson'); - $this->initApi($cfg); + // jquery Autocomplete sends the search as url?term=foo + if(!empty($term)) { + // Leverage-model-specific keyword search - try { - $resultsManagerAdmin = $this->grouperAPI->getUserMemberships($managerId, $managerId, GrouperGroupTypeEnum::ADMIN); - $resultsManagerUpdate = $this->grouperAPI->getUserMemberships($managerId, $managerId, GrouperGroupTypeEnum::UPDATE); - } catch (Exception $e) { - CakeLog::write('error', __METHOD__ . ': An error occurred'); - throw $e; + // Note EmailAddress and Identifier don't support substring search + foreach(array('Name', 'EmailAddress', 'Identifier') as $m) { + $hits = $this->CoPerson->$m->search($coId, $term, 25); + + $coPersonIds = array_merge($coPersonIds, Hash::extract($hits, '{n}.CoPerson.id')); + } } - $managerGroupSet = $this->removeDuplicates($resultsManagerAdmin, $resultsManagerUpdate); + $coPersonIds = array_unique($coPersonIds); - try { - // Groups the user is a member of - $membersGroup = $this->grouperAPI->getUserGroups($managerId, $memberId); - } catch (Exception $e) { - CakeLog::write('error', __METHOD__ . ': An error occurred'); - throw $e; - } + // Look up additional information to provide hints as to which person is which. + // We only do this when there are relatively small numbers of results to + // avoid making a bunch of database queries early in the search. - // Extract the names of the Groups the member-user is a member of - $memberGroupNames = Hash::extract($membersGroup, '{n}.name'); - // Return the groups the user can join and is not a member of - return array_values( // Restart indexing from 0(zero) on the final array - array_filter( // Return the groups the member-user is a member - $managerGroupSet, - static fn($value) => in_array($value['name'], $memberGroupNames) - ) - ); + return $this->dataConstructForPicker($coId, $term,$coPersonIds); } /** - * Potential use was for creating adhoc group by a user, not associated to WG. - * - * Gets all Stems/Folders where User is admin/owner + * 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. * - * @param string $userId + * NOTE: This is a major hack due to Grouper not giving us the right logic for displaying, so have to run all + * groups through a mapped listing of types of Groups in a WG to see if match and then parse and massage to display * - * @return array Array of Stems/Folders from Grouper - * @throws GrouperLiteWidgetException - */ - public function getOwnedStems(string $userId): array - { - try { - return $this->grouperAPI->getUserMemberships($userId, $userId, GrouperGroupTypeEnum::ADMIN); - } catch (Exception $e) { - CakeLog::write('error', __METHOD__ . ': An error occurred'); - throw $e; - } - } + * @param array $groups Listing of Groups + * @return array Listing of Groups in WG format for display - /** - * Get members associated to a specific Grouper Group - * NOTE: This list only shows members, it does not pull in other groups that may be attached in Grouper as - * members - * - * @param string $actorUserId Id of User - * @param string $groupName - * @param array $cfg - * - * @return array Listing of members in requested Grouper Group - * @throws GrouperLiteWidgetException Captured in Controller - * @throws JsonException * @since COmanage Registry v4.4.0 */ - public function getGroupMembers(string $actorUserId, string $groupName, array $cfg): array - { - $this->initApi($cfg); - - try { - $groupMembers = $this->grouperAPI->getGroupMembers($actorUserId, $groupName); - } catch (Exception $e) { - CakeLog::write('error', __METHOD__ . ': An error occurred'); - throw $e; - } - - if (count($groupMembers) < 1) { - return $groupMembers; - } + private function getFriendlyWorkingGroupName(array $groups, string $method) { + $arrayIndex = 0; + $workingGroups = array(); - $finalMembers = []; - foreach ($groupMembers as $member) { - if ($member['sourceId'] !== 'g:gsa') { - $finalMembers[] = $member; + //First need to loop through all groups and pull in all top levels + $topLevelWG = array(); + foreach ($groups as $group) { + foreach ($this->wgStemsTopLevel as $stem) { + $len = strlen($stem); + if (substr(strtolower($group['name']), 0, $len) === $stem) { + $stemSections = explode(':', $group['name']); + //Get a third section, since will always by after ref:something:here + if (in_array($stemSections[2], $topLevelWG) === false) { + $topLevelWG[] = $stemSections[2]; + } + } } - } - return $finalMembers; + + //Loop through groups to see if possibly part of a Working Group + foreach ($groups as &$group) { + foreach ($this->wgStemsAllGroups as $stem) { + $len = strlen($stem); + // if match to name of group within WG mapping then start making a WG group array + if (substr(strtolower($group['name']), 0, $len) === $stem) { + $tempGroup = $group; + if ($stem == 'ref:incommon-collab' || $stem == 'ref:internet2-collab') { + $mainGroup = true; + } else { + $mainGroup = false; + } + $stemSections = explode(':', $group['name']); + $displaySections = explode(':', $group['displayName']); + //Get second to last stem section + $sectionCount = 2; + //If group not part of a top level WG, then do not show! + if (in_array($stemSections[$sectionCount], $topLevelWG) === false) { + break; + } + $tempGroup['WGName'] = $stemSections[$sectionCount]; + $tempGroup['WGShowName'] = $displaySections[$sectionCount]; + // Get user type, which is after the WG name + if (isset($stemSections[$sectionCount + 1])) { + $tempGroup['WGRole'] = $stemSections[$sectionCount + 1]; + } else { + $tempGroup['WGRole'] = ''; + } + $appCount = 0; + $appName = ''; + foreach ($stemSections as $stemSection) { + //Skip first entry + if ($appCount > 0) { + if ($appCount < $sectionCount) { + if ($appCount == 1) { + $appName = $stemSection; + } else { + $appName = $appName . " - " . $stemSection; + } + } + } + $appCount += 1; + } + //changed the way email list are displayed to actually show lists email address. + if ($appName == 'sympa - internet2' || $appName == 'sympa - incommon') { + if ($appName == 'sympa - internet2') { + $appName = $tempGroup['WGName'] . '@lists.' . 'internet2.edu'; + } else { + $appName = $tempGroup['WGName'] . '@lists.' . 'incommon.org'; + } + + } + $tempGroup['WGApp'] = $appName; + if ($method == 'member') { + if(!$mainGroup) { + $workingGroups[] = $tempGroup; + unset($groups[$arrayIndex]); + } + } else { + $workingGroups[] = $tempGroup; + unset($groups[$arrayIndex]); + } + } + } + $arrayIndex += 1; + } + $finalWorkingGroups = array(); + + foreach ($workingGroups as $workingGroup) { + //Need to set first group in final Working Group array + if (count($finalWorkingGroups) == 0) { + $finalWorkingGroups[] = array( + 'WGName' => $workingGroup['WGName'], + 'WGShowName' => $workingGroup['WGShowName'], + 'Groups' => array($workingGroup) + ); + } else { + $foundMatch = false; + foreach ($finalWorkingGroups as &$finalWorkingGroup) { + if ($finalWorkingGroup['WGName'] == $workingGroup['WGName']) { + $finalWorkingGroup['WGShowName'] = $workingGroup['WGShowName']; + $finalWorkingGroup['Groups'][] = $workingGroup; + $foundMatch = true; + } + } + if (!$foundMatch) { + $finalWorkingGroups[] = array( + 'WGName' => $workingGroup['WGName'], + 'WGShowName' => $workingGroup['WGShowName'], + 'Groups' => array($workingGroup) + ); + } + } + } + + $friendlyGroups = array_values($groups); + + //Now need to add the groups back together for one set + foreach ($friendlyGroups as $friendlyGroup) { + $finalWorkingGroups[] = $friendlyGroup; + } + + return $finalWorkingGroups; } /** - * Add a member to a specific Grouper Group + * Retrieve the identifier for a CO Person * - * @param string $actAsUserId Id of User - * @param string $groupName - * @param string $addUserId - * @param array $cfg + * @param int $co_person_id + * @param string $ident_type * - * @return string success of Request - * @throws GrouperLiteWidgetException Captured in Controller - * @throws JsonException - * @since COmanage Registry v4.4.0 + * @return string|null */ - public function addGroupMember(string $actAsUserId, string $groupName, string $addUserId, array $cfg) + public function getIdentifierFromPersonId(int $co_person_id, string $ident_type): ?string { - $this->initApi($cfg); + if(empty($co_person_id) || empty($ident_type)) { + return null; + } + + // Find the identifier + $args = array(); + $args['conditions']['Identifier.type'] = $ident_type; + $args['conditions']['Identifier.status'] = SuspendableStatusEnum::Active; + $args['conditions']['Identifier.co_person_id'] = $co_person_id; + $args['contain'] = false; - return $this->grouperAPI->addGroupMember($actAsUserId, $groupName, $addUserId); + $this->Identifier = ClassRegistry::init('Identifier'); + + $identifier = $this->Identifier->find('first', $args); + if (!empty($identifier)) { + return $identifier['Identifier']['identifier']; + } + + return null; } /** - * Remove a member from a specific Grouper Group + * Get members associated to a specific Grouper Group + * NOTE: This list only shows members, it does not pull in other groups that may be attached in Grouper as + * members * - * @param string $userId Id of User + * @param string $actorUserId Id of User * @param string $groupName - * @param string $removeUserId * @param array $cfg * - * @return bool success of Request + * @return array Listing of members in requested Grouper Group * @throws GrouperLiteWidgetException Captured in Controller - * @throws JsonException Captured in Controller + * @throws JsonException * @since COmanage Registry v4.4.0 */ - public function removeGroupMember(string $userId, - string $groupName, - string $removeUserId, - array $cfg): bool + public function getGroupMembers(string $actorUserId, string $groupName, array $cfg): array { $this->initApi($cfg); - return $this->grouperAPI->removeGroupMember($userId, $groupName, $removeUserId); - } + try { + $groupMembers = $this->grouperAPI->getGroupMembers($actorUserId, $groupName); + } catch (Exception $e) { + CakeLog::write('error', __METHOD__ . ': An error occurred'); + throw $e; + } + + if (count($groupMembers) < 1) { + return $groupMembers; + } + $finalMembers = []; + foreach ($groupMembers as $member) { + if ($member['sourceId'] !== 'g:gsa') { + $finalMembers[] = $member; + } + + } + return $finalMembers; + } /** - * Return all Groups the User can JOIN - * Get all Groups with Optin attribute set and display ones User can join. - * Will Match up with Groups User is already a member of to determine which Optin groups to not display + * Return all Grouper Groups that + * - the User(me) has a role of owner/admin + * - the User (member User) is a member * + * @param string $actorUserId * @param string $userId * @param array $cfg * - * @return array Listing of Optin groups available in Grouper - * @throws GrouperLiteWidgetException Captured in Controller + * @return array + * @throws GrouperLiteWidgetException * @since COmanage Registry v4.4.0 */ - public function optinGroups(string $userId, array $cfg): array - { + public function getManagedUsers(string $actorUserId, string $userId, array $cfg): array { + if(empty($userId) || empty($actorUserId)) { + return false; + } + $this->initApi($cfg); try { - // Groups the user can join or leave - $joinOrLeave = $this->grouperAPI->getUserMemberships($userId, - $userId, - GrouperGroupTypeEnum::OPTINS); + $resultsManagerAdmin = $this->grouperAPI->getUserMemberships($userId, + $actorUserId, + GrouperGroupTypeEnum::ADMIN); + $resultsManagerUpdate = $this->grouperAPI->getUserMemberships($userId, + $actorUserId, + GrouperGroupTypeEnum::UPDATE); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } + $managerGroupSet = $this->removeDuplicates($resultsManagerAdmin, $resultsManagerUpdate); + try { // Groups the user is a member of - $userGroups = $this->grouperAPI->getUserGroups($userId, $userId); + $membersGroup = $this->grouperAPI->getUserGroups($actorUserId, $userId); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } - // I am currently not a member to any Group. Return everything - if(empty($userGroups)) { - return $joinOrLeave; - } - - // Extract the names of the Groups the user is a member of - $userGroupsNames = Hash::extract($userGroups, '{n}.name'); + // Extract the names of the Groups the member-user is a member of + $memberGroupNames = Hash::extract($membersGroup, '{n}.name'); // Return the groups the user can join and is not a member of return array_values( // Restart indexing from 0(zero) on the final array - array_filter( // Return the groups I am currently not a member - $joinOrLeave, - static fn($value) => !in_array($value['name'], $userGroupsNames) + array_filter( // Return the groups the member-user is a member + $managerGroupSet, + static fn($value) => in_array($value['name'], $memberGroupNames) ) ); } /** - * Determine if a User can use the Grouper Template to create a Working Group. + * Return all Grouper Groups that the User has a role of owner/admin * - * @param string $userId User ID - * @param string $groupName Group Name + * @param string $userId + * @param string $actorUserId * @param array $cfg * - * @return bool T for True and F for False + * @return array * @throws GrouperLiteWidgetException * @since COmanage Registry v4.4.0 */ - public function isGroupMember(string $userId, string $groupName, array $cfg): bool + public function getOwnedGroups(string $userId, string $actorUserId, array $cfg): array { - $this->initApi($cfg); - - try { - $isMember = $this->grouperAPI->isMemberOfGroup($groupName, $userId); - } catch (Exception $e) { - CakeLog::write('error', __METHOD__ . ': An error occurred'); - throw $e; + if(empty($userId) || empty($actorUserId)) { + return false; } - return (bool)$isMember; - } - - /** - * Determine if a User can use the Grouper Template to create a Working Group. - * - * @param string $userId User ID - * @return string T for True and F for False - * @throws GrouperLiteWidgetException - * - * @since COmanage Registry v4.4.0 - */ - public function isTemplateUser(string $userId, array $cfg) - { $this->initApi($cfg); try { - $isMember = $this->grouperAPI->isMemberOfGroup(GrouperSpecialGroups::TEMPLATE_CREATION_GROUP, $userId); + $resultsAdmin = $this->grouperAPI->getUserMemberships($userId, + $actorUserId, + GrouperGroupTypeEnum::ADMIN); + $resultsUpdate = $this->grouperAPI->getUserMemberships($userId, + $actorUserId, + GrouperGroupTypeEnum::UPDATE); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } - return $isMember ? 'T' : 'F'; + return $this->removeDuplicates($resultsAdmin, $resultsUpdate); } /** - * Create a new Grouper Group using the Template methodology in Grouper + * Potential use was for creating an adhoc group by a user, not associated to WG. * - * @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 GrouperLiteWidgetException + * Gets all Stems/Folders where User is admin/owner * - * @since COmanage Registry v4.4.0 + * @param string $userId + * @param string $actorUserId + * + * @return array Array of Stems/Folders from Grouper + * @throws GrouperLiteWidgetException */ - public function createGroupWithTemplate(string $userId, array $groupData, array $cfg) + public function getOwnedStems(string $userId, string $actorUserId): array { - $this->initApi($cfg); - //Need to massage incoming data to meet Grouper Template requirements - $fields = array( - 'gsh_input_isSympa', - 'gsh_input_isSympaModerated', - 'gsh_input_isOptin', - 'gsh_input_isConfluence', - 'gsh_input_isJira' - ); - // Template does not except true/false, so convert to string and send that way - foreach ($fields as $field) { - ($groupData[$field] == '0') ? $groupData[$field] = 'false' : $groupData[$field] = 'true'; - } - - $args = array(); - $args['userId'] = $userId; - $args['data'] = $groupData; try { - return $this->grouperAPI->createGroupWithTemplate($args); + return $this->grouperAPI->getUserMemberships($userId, + $actorUserId, + GrouperGroupTypeEnum::ADMIN); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; @@ -635,22 +698,27 @@ public function createGroupWithTemplate(string $userId, array $groupData, array * Search for Groups/Lists related to Search term. * * Will import all records the user can see and then do search in this code rather than call Grouper WS Search - * functionality. This is due to the fact that the grouperName is autogenerated and this app needs to search + * functionality. This is because the grouperName is autogenerated and this app needs to search * attributes which the Grouper WS does not do. * - * @param array $conditions Listing of conditions for display of records, including UserId + * @param string $userId + * @param string $searchCriteria + * @param string $searchPage + * @param array $cfg + * * @return array Records that meet search criteria - * @throws Exception Captured in Controller - + * @throws GrouperLiteWidgetException * @since COmanage Registry v4.4.0 */ - public function getSearchedGroups(string $userId, string $searchCriteria, string $searchPage, array $cfg) + public function getSearchedGroups(string $userId, string $actorUserId, string $searchCriteria, string $searchPage, array $cfg): array { $this->initApi($cfg); try { // Breakout page where search was called and forward to appropriate method for processing - $pageResults = isset($searchPage) ? $this->$searchPage($userId, $cfg) : []; + $pageResults = isset($searchPage) ? $this->$searchPage(userId: $userId, + actorUserId: $actorUserId, + cfg: $cfg) : []; $returnResults = []; @@ -668,7 +736,7 @@ public function getSearchedGroups(string $userId, string $searchCriteria, string } return $searchCriteria == 'getSearchedGroups' ? $this->getFriendlyWorkingGroupName($returnResults, 'member') - : $returnResults; + : $returnResults; } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); @@ -677,142 +745,148 @@ public function getSearchedGroups(string $userId, string $searchCriteria, string } /** - * 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. + * Determine if a User can use the Grouper Template to create a Working Group. * - * NOTE: This is a major hack due to Grouper not giving us the right logic for displaying, so have to run all - * groups through a mapped listing of types of Groups in a WG to see if match and then parse and massage to display + * @param string $userId User ID + * @param string $groupName Group Name + * @param array $cfg * - * @param array $groups Listing of Groups - * @return array Listing of Groups in WG format for display + * @return bool T for True and F for False + * @throws GrouperLiteWidgetException + * @since COmanage Registry v4.4.0 + */ + public function isGroupMember(string $userId, string $groupName, array $cfg): bool + { + $this->initApi($cfg); + + try { + $isMember = $this->grouperAPI->isMemberOfGroup($groupName, $userId); + } catch (Exception $e) { + CakeLog::write('error', __METHOD__ . ': An error occurred'); + throw $e; + } + + return (bool)$isMember; + } + /** + * Determine if a User can use the Grouper Template to create a Working Group. + * + * @param string $userId User ID + * @param array $cfg + * + * @return bool + * @throws GrouperLiteWidgetException * @since COmanage Registry v4.4.0 */ - private function getFriendlyWorkingGroupName(array $groups, string $method) { - $arrayIndex = 0; - $workingGroups = array(); + public function isTemplateUser(string $userId, array $cfg): bool + { + $this->initApi($cfg); - //First need to loop through all groups and pull in all top levels - $topLevelWG = array(); - foreach ($groups as $group) { - foreach ($this->wgStemsTopLevel as $stem) { - $len = strlen($stem); - if (substr(strtolower($group['name']), 0, $len) === $stem) { - $stemSections = explode(':', $group['name']); - //Get third section, since will always by after ref:something:here - if (in_array($stemSections[2], $topLevelWG) === false) { - $topLevelWG[] = $stemSections[2]; - } - } - } + try { + return $this->grouperAPI->isMemberOfGroup(GrouperSpecialGroups::TEMPLATE_CREATION_GROUP, $userId); + } catch (Exception $e) { + CakeLog::write('error', __METHOD__ . ': An error occurred'); + throw $e; } + } - //Loop through groups to see if possibly part of a Working Group - foreach ($groups as &$group) { - foreach ($this->wgStemsAllGroups as $stem) { - $len = strlen($stem); - // if match to name of group within WG mapping then start making a WG group array - if (substr(strtolower($group['name']), 0, $len) === $stem) { - $tempGroup = $group; - if ($stem == 'ref:incommon-collab' || $stem == 'ref:internet2-collab') { - $mainGroup = true; - } else { - $mainGroup = false; - } - $stemSections = explode(':', $group['name']); - $displaySections = explode(':', $group['displayName']); - //Get second to last stem section - $sectionCount = 2; - //If group not part of a top level WG, then do not show! - if (in_array($stemSections[$sectionCount], $topLevelWG) === false) { - break; - } - $tempGroup['WGName'] = $stemSections[$sectionCount]; - $tempGroup['WGShowName'] = $displaySections[$sectionCount]; - // Get user type, which is after the WG name - if (isset($stemSections[$sectionCount + 1])) { - $tempGroup['WGRole'] = $stemSections[$sectionCount + 1]; - } else { - $tempGroup['WGRole'] = ''; - } - $appCount = 0; - $appName = ''; - foreach ($stemSections as $stemSection) { - //Skip first entry - if ($appCount > 0) { - if ($appCount < $sectionCount) { - if ($appCount == 1) { - $appName = $stemSection; - } else { - $appName = $appName . " - " . $stemSection; - } - } - } - $appCount += 1; - } - //changed the way email list are displayed to actually show lists email address. - if ($appName == 'sympa - internet2' || $appName == 'sympa - incommon') { - if ($appName == 'sympa - internet2') { - $appName = $tempGroup['WGName'] . '@lists.' . 'internet2.edu'; - } else { - $appName = $tempGroup['WGName'] . '@lists.' . 'incommon.org'; - } + /** + * Internal process used by other functions to fetch Groups the User is a member of + * + * @param string $actorUserId + * @param string $userId + * @param array $cfg + * + * @return array Records of Groups from Grouper that the User belongs to + * @throws GrouperLiteWidgetException + * @since COmanage Registry v4.4.0 + */ + private function memberOfGroups(string $actorUserId, string $userId, array $cfg) + { + $this->initApi($cfg); - } - $tempGroup['WGApp'] = $appName; - if ($method == 'member') { - if(!$mainGroup) { - $workingGroups[] = $tempGroup; - unset($groups[$arrayIndex]); - } - } else { - $workingGroups[] = $tempGroup; - unset($groups[$arrayIndex]); - } - } - } - $arrayIndex += 1; + try { + return $this->grouperAPI->getUserGroups($actorUserId, $userId); + } catch (Exception $e) { + CakeLog::write('error', __METHOD__ . ': An error occurred'); + throw $e; } - $finalWorkingGroups = array(); + } - foreach ($workingGroups as $workingGroup) { - //Need to set first group in final Working Group array - if (count($finalWorkingGroups) == 0) { - $finalWorkingGroups[] = array( - 'WGName' => $workingGroup['WGName'], - 'WGShowName' => $workingGroup['WGShowName'], - 'Groups' => array($workingGroup) - ); - } else { - $foundMatch = false; - foreach ($finalWorkingGroups as &$finalWorkingGroup) { - if ($finalWorkingGroup['WGName'] == $workingGroup['WGName']) { - $finalWorkingGroup['WGShowName'] = $workingGroup['WGShowName']; - $finalWorkingGroup['Groups'][] = $workingGroup; - $foundMatch = true; - } - } - if (!$foundMatch) { - $finalWorkingGroups[] = array( - 'WGName' => $workingGroup['WGName'], - 'WGShowName' => $workingGroup['WGShowName'], - 'Groups' => array($workingGroup) - ); - } - } + /** + * Return all Groups the User can JOIN + * Get all Groups with Optin attribute set and display ones User can join. + * Will Match up with Groups User is already a member of to determine which Optin groups to not display + * + * @param string $userId + * @param string $actorUserId + * @param array $cfg + * + * @return array Listing of Optin groups available in Grouper + * @throws GrouperLiteWidgetException Captured in Controller + * @since COmanage Registry v4.4.0 + */ + public function optinGroups(string $userId, string $actorUserId, array $cfg): array + { + $this->initApi($cfg); + + try { + // Groups the user can join or leave + $joinOrLeave = $this->grouperAPI->getUserMemberships($userId, + $actorUserId, + GrouperGroupTypeEnum::OPTINS); + } catch (Exception $e) { + CakeLog::write('error', __METHOD__ . ': An error occurred'); + throw $e; } - $friendlyGroups = array_values($groups); + try { + // Groups the user is a member of + $userGroups = $this->grouperAPI->getUserGroups($actorUserId, $userId); + } catch (Exception $e) { + CakeLog::write('error', __METHOD__ . ': An error occurred'); + throw $e; + } - //Now need to add the groups back together for one set - foreach ($friendlyGroups as $friendlyGroup) { - $finalWorkingGroups[] = $friendlyGroup; + // I am currently not a member to any Group. Return everything + if(empty($userGroups)) { + return $joinOrLeave; } - return $finalWorkingGroups; + // 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_values( // Restart indexing from 0(zero) on the final array + array_filter( // Return the groups I am currently not a member + $joinOrLeave, + static fn($value) => !in_array($value['name'], $userGroupsNames) + ) + ); } + /** + * Remove a member from a specific Grouper Group + * + * @param string $actorUserId + * @param string $groupName + * @param string $removeUserId + * @param array $cfg + * + * @return bool success of Request + * @throws GrouperLiteWidgetException Captured in Controller + * @throws JsonException Captured in Controller + * @since COmanage Registry v4.4.0 + */ + public function removeGroupMember(string $actorUserId, + string $groupName, + string $removeUserId, + array $cfg): bool + { + $this->initApi($cfg); + + return $this->grouperAPI->removeGroupMember($actorUserId, $groupName, $removeUserId); + } /** * Removes duplicates where the user is the owner and the updater of the group. Just one line instead of two. @@ -841,31 +915,4 @@ public function removeDuplicates(array $arrOne, array $arrTwo) return $uniqueArr; } - - - /** - * Breakout Working Groups from AdHoc Groups. - * - * @param array $recordSet - * @return array[] - * - */ - public function breakoutWrkFromAdHocGroups(array $recordSet): array - { - $wgData = []; - $notWGData = []; - //Parse out the Working Groups from the Ad-hoc groups - foreach ($recordSet as $record) { - if (isset($record['WGName'])) { - $wgData[] = $record; - } else { - $notWGData[] = $record; - } - } - - return [ - 'adhoc' => $notWGData, - 'working' => $wgData - ]; - } }