diff --git a/Controller/GrouperGroupsController.php b/Controller/GrouperGroupsController.php index 659496b..79d3515 100644 --- a/Controller/GrouperGroupsController.php +++ b/Controller/GrouperGroupsController.php @@ -107,7 +107,7 @@ public function beforeRender() { $this->set('vv_config', $cfg); $this->set('title', _txt('pl.grouperlite.title.groupmember')); - $this->set('vv_is_user_owner', $this->GrouperGroup->isUserOwner($this->userId ?? '', $cfg) ); + $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']); @@ -149,31 +149,24 @@ public function groupOwner(): void * Show all members of a group * Called from all pages via AJAX call * + * @throws Exception */ public function groupSubscribers(): void { $groupName = urldecode($this->request->query['groupname']); - $subscribers = 0; + $subscribers = []; // //Need to see if coming from AdHoc or from a WG (Working Group) // $groupNameFormatted = strpos($groupName, ':') === false ? 'ref:incommon-collab:' . $groupName . ':users' // : $groupName; try { - $subscribers = $this->GrouperGroup->getGrouperGroupMembers($this->userId, - $groupName, - $this->CoGrouperLiteWidget->getConfig()); + $subscribers = $this->GrouperGroup->getGroupMembers($this->userId, + $groupName, + $this->CoGrouperLiteWidget->getConfig()); } 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(count($subscribers) < 1){ - $this->restResponse(HttpStatusCodesEnum::HTTP_NOT_FOUND, ErrorsEnum::Error); - } elseif (count($subscribers) == 1 - && $subscribers[0]['sourceId'] === 'NoAccess') { - $this->restResponse(HttpStatusCodesEnum::HTTP_FORBIDDEN, ErrorsEnum::NoAccess); + throw $e; } $this->set(compact('subscribers')); @@ -184,31 +177,38 @@ public function groupSubscribers(): void * Add a new member to a group * Called from all pages via AJAX call * + * @throws JsonException + * @throws Exception */ public function addSubscriber(): void { + $this->layout = null; + $this->autoRender = false; + $groupName = urldecode($this->request->query['group']); $addUserId = urldecode($this->request->query['userId']); - $resultAdd = false; - //Need to see if coming from AdHoc or from a WG (Working Group) + // Need to see if coming from AdHoc or from a WG (Working Group) + // todo: Investigate further + // XXX groupJoin is not using this formatted syntax??? // $groupNameFormatted = strpos($groupName, ':') === false ? 'ref:incommon-collab:' . $groupName . ':users' // : $groupName; try { - $resultAdd = $this->GrouperGroup->addGrouperGroupMember($this->userId, - $groupName, - $addUserId, - $this->CoGrouperLiteWidget->getConfig()); + if(!$this->GrouperGroup->addGroupMember($this->userId, + $groupName, + $addUserId, + $this->CoGrouperLiteWidget->getConfig())) { + // The Request returned unsuccessful, but we have not more infomration. In this case we will just return + // forbidden since we do not actually now what happened + $this->restResponse(HttpStatusCodesEnum::HTTP_FORBIDDEN); + } } catch (Exception $e) { -// $this->restResponse(HttpStatusCodesEnum::HTTP_UNAUTHORIZED, ErrorsEnum::NotAdded); CakeLog::write('error', __METHOD__ . ': ' . var_export($e->getMessage(), true)); - $this->restResponse(HttpStatusCodesEnum::HTTP_NOT_FOUND, ErrorsEnum::Exception); + throw $e; } - $this->set(compact($resultAdd ? GrouperResultCodesEnum::SUCCESS : '')); - $this->set('_serialize', 'resultAdd'); - + $this->restResponse(HttpStatusCodesEnum::HTTP_CREATED); } /** @@ -243,32 +243,35 @@ public function findSubscriber(): void * Called from all pages via AJAX call * * TODO: We need to appropriately handle Unathenticated call. We have to bubble up the response and do something. + * @throws JsonException */ public function removeSubscriber(): void { + $this->layout = null; + $this->autoRender = false; + $groupName = urldecode($this->request->query['group']); $remUserId = urldecode($this->request->query['userId']); - $resultRemove = false; //Need to see if coming from AdHoc or from a WG (Working Group) - $groupNameFormatted = (strpos($groupName, ':') === false) ? 'ref:incommon-collab:' . $groupName . ':users' - : $groupName; +// $groupNameFormatted = (strpos($groupName, ':') === false) ? 'ref:incommon-collab:' . $groupName . ':users' +// : $groupName; try { - $resultRemove = $this->GrouperGroup->removeGrouperGroupMember($this->userId, - $groupNameFormatted, - $remUserId, - $this->CoGrouperLiteWidget->getConfig()); + if(!$this->GrouperGroup->removeGroupMember($this->userId, + $groupName, + $remUserId, + $this->CoGrouperLiteWidget->getConfig())) { + // The Request returned unsuccessful, but we have not more infomration. In this case we will just return + // forbidden since we do not actually now what happened + $this->restResponse(HttpStatusCodesEnum::HTTP_FORBIDDEN); + } } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': ' . var_export($e->getMessage(), true)); + throw $e; } - if (!$resultRemove) { - $this->restResponse(HttpStatusCodesEnum::HTTP_NOT_FOUND, ErrorsEnum::Error); - } - - $this->set(compact($resultRemove ? GrouperResultCodesEnum::SUCCESS : '')); - $this->set('_serialize', 'resultRemove'); + $this->restResponse(HttpStatusCodesEnum::HTTP_OK); } /** @@ -289,7 +292,7 @@ public function groupOwnerApi() { $scope['searchpage'] = 'ownerGroups'; $errorHint = 'Search'; } else { - $scope['method'] = 'ownerGroups'; + $scope['method'] = 'getOwnedGroups'; $errorHint = ''; } try { @@ -429,25 +432,32 @@ public function groupCreateTemplate() /** * Process to join a group displayed on the "Optin" page * + * @throws Exception */ public function joinGroup(): void { - $name = urldecode($this->request->query['GroupName']); - $resultAdd = false; + $this->layout = null; + $this->autoRender = false; + // todo: add Subscriber and joinGroup should accept the same query parameters. Currently the join Group + // accepts a GroupName, while the addSubscriber accepts a group parameter + $groupName = urldecode($this->request->query['GroupName']); try { // Add myself - $resultAdd = $this->GrouperGroup->joinGroup($this->userId, - $name, - $this->CoGrouperLiteWidget->getConfig()); + if(!$this->GrouperGroup->addGroupMember($this->userId, + $groupName, + $this->userId, + $this->CoGrouperLiteWidget->getConfig())) { + // The Request returned unsuccessful, but we have not more infomration. In this case we will just return + // forbidden since we do not actually now what happened + $this->restResponse(HttpStatusCodesEnum::HTTP_FORBIDDEN); + } } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': ' . var_export($e->getMessage(), true)); -// $this->restResponse(HttpStatusCodesEnum::HTTP_UNAUTHORIZED, ErrorsEnum::NotAdded); - $this->restResponse(HttpStatusCodesEnum::HTTP_NOT_FOUND, ErrorsEnum::Exception); + throw $e; } - $this->set(compact($resultAdd ? GrouperResultCodesEnum::SUCCESS : '')); - $this->set('_serialize', 'resultAdd'); + $this->restResponse(HttpStatusCodesEnum::HTTP_CREATED); } /** @@ -456,25 +466,25 @@ public function joinGroup(): void */ public function leaveGroup(): void { - $name = urldecode($this->request->query['GroupName']); - $resultRemove = false; + $this->layout = null; + $this->autoRender = false; + $groupName = urldecode($this->request->query['GroupName']); try { - $resultRemove = $this->GrouperGroup->leaveGroup($this->userId, - $name, - $this->CoGrouperLiteWidget->getConfig()); + if(!$this->GrouperGroup->removeGroupMember($this->userId, + $groupName, + $this->userId, + $this->CoGrouperLiteWidget->getConfig())) { + // The Request returned unsuccessful, but we have not more infomration. In this case we will just return + // forbidden since we do not actually now what happened + $this->restResponse(HttpStatusCodesEnum::HTTP_FORBIDDEN); + } } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': ' . var_export($e->getMessage(), true)); + throw $e; } -// $this->restResponse(HttpStatusCodesEnum::HTTP_UNAUTHORIZED, ErrorsEnum::NotDeleted); - - if (!$resultRemove) { - $this->restResponse(HttpStatusCodesEnum::HTTP_NOT_FOUND, ErrorsEnum::Error); - } - - $this->set(compact($resultRemove ? GrouperResultCodesEnum::SUCCESS : '')); - $this->set('_serialize', 'resultRemove'); + $this->restResponse(HttpStatusCodesEnum::HTTP_OK); } /** diff --git a/Lib/GrouperApiAccess.php b/Lib/GrouperApiAccess.php index c84e8de..abf1726 100644 --- a/Lib/GrouperApiAccess.php +++ b/Lib/GrouperApiAccess.php @@ -35,14 +35,11 @@ */ class GrouperApiAccess { - // Array for storing settings needed in call to Grouper - private $config; - // Instance of Grouper HTTP Wrapper class - private $http; + private GrouperHTTPWrapper $http; // String used to build out Grouper WS URL - private $_urlServlet = '/grouper-ws/servicesRest/'; + private string $_urlServlet = '/grouper-ws/servicesRest/'; /** * GrouperApiAccess constructor. @@ -63,7 +60,7 @@ public function __construct(array $cfg) || empty($cfg['CoGrouperLiteWidget']['conn_pass']) || empty($cfg['CoGrouperLiteWidget']['conn_ver']) ) { - throw new GrouperLiteWidgetException('No GrouperLite instance captured'); + throw new \http\Exception\RuntimeException('No GrouperLite instance captured'); } $this->http->setServiceUrl($cfg['CoGrouperLiteWidget']['conn_url'], $cfg['CoGrouperLiteWidget']['conn_ver']); @@ -81,12 +78,11 @@ public function __construct(array $cfg) * @param string $addUserId * * @return bool Requests success or not - * @throws GrouperLiteWidgetException - * @throws JsonException + * @throws GrouperLiteWidgetException|JsonException */ - public function addGrouperGroupMember(string $actAsUserId, - string $groupName, - string $addUserId): bool + public function addGroupMember(string $actAsUserId, + string $groupName, + string $addUserId): bool { if(empty($actAsUserId) || empty($groupName) @@ -121,6 +117,11 @@ public function addGrouperGroupMember(string $actAsUserId, throw $e; } + if(isset($results['error']) && $results['error']) { + $cakeExceptionClass = $results['cakeException']; + throw new $cakeExceptionClass($results['message']); + } + return isset($results['WsAddMemberResults']['wsGroupAssigned']) && $results['WsAddMemberResults']['wsGroupAssigned']['name'] === urldecode($groupName); } @@ -262,7 +263,7 @@ public function createUpdateGroup(string $actAsUserId, string $groupName, string * @throws GrouperLiteWidgetException|JsonException * */ - public function deleteGroupWithTemplate(array $queryData) + public function deleteGroupWithTemplate(array $queryData): bool { $workingGroupExt = $queryData['workingGroupExt']; $userId = $queryData['userId']; @@ -320,7 +321,7 @@ public function deleteGroupWithTemplate(array $queryData) * @throws GrouperLiteWidgetException * @throws JsonException */ - public function getGrouperGroupInfo(string $groupName) + public function getGroupInfo(string $groupName): array { $groupInfo = []; @@ -371,7 +372,7 @@ public function getGrouperGroupInfo(string $groupName) * * @throws GrouperLiteWidgetException */ - public function getUserGrouperGroupMemberships(string $actorUserId, string $userId): array + public function getUserGroupMemberships(string $actorUserId, string $userId): array { if(empty($userId)) { return []; @@ -390,57 +391,6 @@ public function getUserGrouperGroupMemberships(string $actorUserId, string $user return $results['WsGetGroupsLiteResult']['wsGroups'] ?? []; } - /** - * Get members associated to a specific Grouper Group - * - * @param string $actorUserId - * @param string $groupName - * - * @return array Listing of Members belonging to Grouper Group - * @throws GrouperLiteWidgetException - * @throws JsonException - */ - public function getGrouperGroupMembers(string $actorUserId, string $groupName): array - { - //Build request logic - $usersToShow = [ - 'WsRestGetMembersRequest' => [ - 'actAsSubjectLookup' => [ - 'subjectId' => $actorUserId - ], - 'wsGroupLookups' => [ - ['groupName' => $groupName] - ], - 'subjectAttributeNames' => ['name'] - ] - ]; - - $actionEndpoint = "/groups"; - - try { - $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; - } - - // Parse out relevant records to send front end - if(isset($results['WsGetMembersResults']['results'][0]['resultMetadata']['resultCode']) - && $results['WsGetMembersResults']['results'][0]['resultMetadata']['resultCode'] === GrouperResultCodesEnum::GROUP_NOT_FOUND) { - return [ - [ - 'sourceId' => 'NoAccess', - 'name' => '', - 'id' => '' - ] - ]; - } - - return $results['WsGetMembersResults']['results'][0]['wsSubjects'] ?? []; - } - /** * Used for requests made to Membership endpoint in Grouper WS * @@ -454,31 +404,30 @@ public function getGrouperGroupMembers(string $actorUserId, string $groupName): * @see getOptOutGroups() * @see getOwnedGroups() */ - private function getGrouperUserMemberships(string $userId, string $groupType): array + public function getGrouperUserMemberships(string $userId, string $groupType): array { - if (in_array($groupType, [ + if(!in_array($groupType, [ GrouperGroupTypeEnum::OPTINS, - GrouperGroupTypeEnum::OPTOUTS], true) - ) { - $subjectId = 'GrouperAll'; - } elseif (in_array($groupType, [ + GrouperGroupTypeEnum::OPTOUTS, GrouperGroupTypeEnum::ADMIN, GrouperGroupTypeEnum::UPDATE, - GrouperGroupTypeEnum::STEM_ADMIN], true) + GrouperGroupTypeEnum::STEM_ADMIN + ], true) ) { - $subjectId = $userId; - } else { CakeLog::write('error', __METHOD__ . ": Option of {$groupType} is not supported"); - throw new GrouperLiteWidgetException("Option of {$groupType} is not supported"); + throw new BadRequestException("Option of {$groupType} is not supported"); } - //Build request logic + $isOptinsOrOptouts = in_array($groupType, + [GrouperGroupTypeEnum::OPTINS, GrouperGroupTypeEnum::OPTOUTS], + true); + + // Build request logic $data = []; $data['WsRestGetMembershipsRequest']['fieldName'] = $groupType; - $data['WsRestGetMembershipsRequest']['wsSubjectLookups'][0]['subjectId'] = $subjectId; + $data['WsRestGetMembershipsRequest']['wsSubjectLookups'][0]['subjectId'] = $isOptinsOrOptouts ? GrouperConfigEnums::ALL : $userId; - if ($groupType == GrouperGroupTypeEnum::OPTINS - || $groupType == GrouperGroupTypeEnum::OPTOUTS) { + if ($isOptinsOrOptouts) { // Build request logic, 2 subjectId's, second is for when user in "Secret" Optin/Optout Group $data['WsRestGetMembershipsRequest']['wsSubjectLookups'][1]['subjectId'] = $userId; } @@ -498,47 +447,68 @@ private function getGrouperUserMemberships(string $userId, string $groupType): a } /** - * Gets all available Optin/OptOut groups in Grouper - * - * Returns Optin/OptOut groups that can be joined/left + * Get members associated to a specific Grouper Group * - * @param string $userId - * @param string $groupType + * @param string $actorUserId + * @param string $groupName * - * @return array Optin groups from Grouper - * @throws GrouperLiteWidgetException + * @return array Listing of Members belonging to Grouper Group + * @throws GrouperLiteWidgetException|JsonException|NotFoundException */ - public function getOptionalGroups(string $userId, string $groupType): array + public function getGroupMembers(string $actorUserId, string $groupName): array { + //Build request logic + $usersToShow = [ + 'WsRestGetMembersRequest' => [ + 'actAsSubjectLookup' => [ + 'subjectId' => $actorUserId + ], + 'wsGroupLookups' => [ + ['groupName' => $groupName] + ], + 'subjectAttributeNames' => ['name'] + ] + ]; + + $actionEndpoint = '/groups'; + try { - $results = $this->getGrouperUserMemberships($userId, $groupType); + $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 $results['WsGetMembershipsResults']['wsGroups'] ?? []; + + if(isset($results['error']) && $results['error']) { + $cakeExceptionClass = $results['cakeException']; + throw new $cakeExceptionClass($results['message']); + } + + return $results['WsGetMembersResults']['results'][0]['wsSubjects'] ?? []; } /** - * Gets all groups in Grouper where user is an admin/owner or has update privs + * Gets all available Optin/OptOut groups in Grouper + * + * Returns Optin/OptOut groups that can be joined/left * * @param string $userId + * @param string $groupType * - * @return array Array of groups from Grouper - * @throws Exception + * @return array Optin groups from Grouper + * @throws GrouperLiteWidgetException */ - public function getOwnedGroups(string $userId): array + public function getOptionalGroups(string $userId, string $groupType): array { try { - $resultsAdmin = $this->getGrouperUserMemberships($userId, GrouperGroupTypeEnum::ADMIN); - $resultsUpdate = $this->getGrouperUserMemberships($userId, GrouperGroupTypeEnum::UPDATE); + $results = $this->getGrouperUserMemberships($userId, $groupType); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } - - return $this->removeDuplicates($resultsAdmin['WsGetMembershipsResults']['wsGroups'] ?? [], - $resultsUpdate['WsGetMembershipsResults']['wsGroups'] ?? []); + return $results['WsGetMembershipsResults']['wsGroups'] ?? []; } /** @@ -572,7 +542,7 @@ public function getOwnedStems(string $userId): array * * @throws GrouperLiteWidgetException */ - public function isMemberOfGrouperGroup(string $groupName, string $userId): bool + public function isMemberOfGroup(string $groupName, string $userId): bool { if(empty($userId) || empty($groupName)) { return []; @@ -580,7 +550,7 @@ public function isMemberOfGrouperGroup(string $groupName, string $userId): bool $groupNameEncoded = $this->urlGrouperEncode($groupName); - $actionEndpoint = "/groups" + $actionEndpoint = '/groups' . "/{$groupNameEncoded}/members/{$userId}"; try { $results = $this->http->sendRequest('GET', $actionEndpoint); @@ -593,37 +563,6 @@ public function isMemberOfGrouperGroup(string $groupName, string $userId): bool && $results['WsHasMemberLiteResul']['resultMetadata']['resultCode'] === GrouperResultCodesEnum::IS_MEMBER; } - /** - * 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; - } - - foreach ($arrL as $large) { - foreach ($arrS as $key => $val) { - if ($large['uuid'] == $val['uuid']) { - unset($arrS[$key]); - } - } - } - - return array_merge_recursive($arrL, $arrS); - } - /** * Remove a member from a specific Grouper Group * @@ -636,9 +575,7 @@ public function removeDuplicates(array $arrOne, array $arrTwo) * @throws GrouperLiteWidgetException * @throws JsonException */ - public function removeGrouperGroupMember(string $actAsUserId, - string $groupName, - string $removeUserId): bool + public function removeGroupMember(string $actAsUserId, string $groupName, string $removeUserId): bool { if(empty($actAsUserId) || empty($groupName) @@ -672,7 +609,11 @@ public function removeGrouperGroupMember(string $actAsUserId, throw $e; } - // Parse out relevant records to send front end + if(isset($results['error']) && $results['error']) { + $cakeExceptionClass = $results['cakeException']; + throw new $cakeExceptionClass($results['message']); + } + return isset($results['WsDeleteMemberResults']['resultMetadata']['resultCode']) && $results['WsDeleteMemberResults']['resultMetadata']['resultCode'] === GrouperResultCodesEnum::SUCCESS; } diff --git a/Lib/GrouperHTTPWrapper.php b/Lib/GrouperHTTPWrapper.php index 727ae79..8475837 100644 --- a/Lib/GrouperHTTPWrapper.php +++ b/Lib/GrouperHTTPWrapper.php @@ -190,23 +190,46 @@ public function sendRequest(string $method, string $endPoint, string $body = '') $apiResults = $this->request($this->_request); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred: ' . var_export($e->getMessage(), true)); - throw new GrouperLiteWidgetException('An error occurred talking to Grouper WS'); + throw new RuntimeException(_txt('er.notfound-b', array( + _txt('pl.grouperlite.crumb.root')) + )); } - // Call may return non-200, which may be okay depending on call - if (!$apiResults->isOk()) { - CakeLog::write('error', __METHOD__ . ': Grouper WS returned non-200 of ' . var_export($apiResults->body(), true)); + $successHeader = $apiResults->getHeader('X-Grouper-success'); + if (empty($successHeader)) { + throw new RuntimeException('Web service did not even respond!'); } $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!'); + $code = $apiResults->code; + $reasonPhrase = $apiResults->reasonPhrase; + + // The request returned with Failed status + if($successHeader == 'F' && !empty($payload)) { + CakeLog::write('debug', __METHOD__ . '::body ' . var_export($apiResults->body(), true)); + CakeLog::write('debug', __METHOD__ . '::headers: ' . var_export($apiResults->headers, true)); + + // I need to pop the element since the key is request specific. + // For example, it is WsAddMemberResults for an Add request + $error_payload = array_pop($payload); + $constant_error = 'GrouperCodesToExceptionClassEnum::' . $error_payload['resultMetadata']['resultCode']; + $cakeException = defined($constant_error) ? constant($constant_error) : 'BadRequestException'; + if($code == HttpStatusCodesEnum::HTTP_UNAUTHORIZED) { + $cakeException = 'UnauthorizedException'; + } + + return [ + 'error' => true, + 'grouperCode' => $error_payload['resultMetadata']['resultCode'], + 'message' => $error_payload['resultMetadata']['resultMessage'], + 'reasonPhrase' => $reasonPhrase, + 'cakeException' => $cakeException + ]; } +// CakeLog::write('debug', __METHOD__ . '::payload: ' . var_export($payload, true)); + return $payload; } } diff --git a/Lib/enum.php b/Lib/enum.php index 3d84546..963dae1 100644 --- a/Lib/enum.php +++ b/Lib/enum.php @@ -1,11 +1,11 @@ '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'); + const Error = ['status' => 'ERROR', 'message' => 'ERROR']; + const Exception = ['status' => 'ERROR', 'message' => 'EXCEPTION']; + const NoAccess = ['status' => 'ERROR', 'message' => 'NO ACCESS']; + const NotAdded = ['status' => 'ERROR', 'message' => 'NOT ADDED']; + const NotDeleted = ['status' => 'ERROR', 'message' => 'NOT DELETED']; } class GrouperSpecialGroups { @@ -20,19 +20,40 @@ class GrouperSpecialGroups { } class GrouperResultCodesEnum { - const IS_MEMBER = 'IS_MEMBER'; - const SUCCESS = 'SUCCESS'; + const SUCCESS_ALREADY_EXISTED = 'SUCCESS_ALREADY_EXISTED'; + const EXECUTE_FAILED = 'EXECUTE_FAILED'; const GROUP_NOT_FOUND = 'GROUP_NOT_FOUND'; - const SUBJECT_NOT_FOUND = 'SUBJECT_NOT_FOUND'; + const IS_MEMBER = 'IS_MEMBER'; + const IS_NOT_MEMBER = 'IS_NOT_MEMBER'; const NO_SUCH_OBJECT = 'NO_SUCH_OBJECT'; + const PROBLEM_WITH_ASSIGNMENT = 'PROBLEM_WITH_ASSIGNMENT'; + const PROBLEM_GETTING_MEMBERS = 'PROBLEM_GETTING_MEMBERS'; + const SUBJECT_NOT_FOUND = 'SUBJECT_NOT_FOUND'; + const SUCCESS = 'SUCCESS'; + const SUCCESS_INSERTED = 'SUCCESS_INSERTED'; + const SUCCESS_NO_CHANGES_NEEDED = 'SUCCESS_NO_CHANGES_NEEDED'; const SUCCESS_NO_INFO = 'SUCCESS_NO_INFO'; - const EXECUTE_FAILED = 'EXECUTE_FAILED'; + const SUCCESS_UPDATED = 'SUCCESS_UPDATED'; +} + +class GrouperCodesToExceptionClassEnum { + const EXECUTE_FAILED = 'BadRequestException'; + const GROUP_NOT_FOUND = 'NotFoundException'; + const NO_SUCH_OBJECT = 'NotFoundException'; + const PROBLEM_WITH_ASSIGNMENT = 'BadRequestException'; + const SUBJECT_NOT_FOUND = 'NotFoundException'; + const SUCCESS_NO_INFO = 'MethodNotAllowedException'; } class GrouperGroupTypeEnum { + const ADMIN = 'admin'; const OPTINS = 'optins'; const OPTOUTS = 'optouts'; - const ADMIN = 'admin'; - const UPDATE = 'update'; const STEM_ADMIN = 'stemAdmin'; + const UPDATE = 'update'; +} + +class GrouperConfigEnums { + // https://github.com/Internet2/grouper/blob/GROUPER_4_BRANCH/grouper/src/grouper/edu/internet2/middleware/grouper/cfg/GrouperConfig.java + const ALL = 'GrouperAll'; } \ No newline at end of file diff --git a/Model/GrouperGroup.php b/Model/GrouperGroup.php index 2b52896..1a1f8b6 100644 --- a/Model/GrouperGroup.php +++ b/Model/GrouperGroup.php @@ -70,19 +70,24 @@ class GrouperGroup extends GrouperLiteWidgetAppModel * * @since COmanage Registry v4.4.0 */ - public function isUserOwner(string $userId, array $cfg) + public function isUserGroupOwner(string $userId, array $cfg): bool { $this->initApi($cfg); - $args = array(); + + if(empty($userId)) { + return false; + } try { - $ownGroups = $this->grouperAPI->getOwnedGroups($userId); + $resultsAdmin = $this->grouperAPI->getGrouperUserMemberships($userId, GrouperGroupTypeEnum::ADMIN); + $resultsUpdate = $this->grouperAPI->getGrouperUserMemberships($userId, GrouperGroupTypeEnum::UPDATE); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } - return count($ownGroups) > 0 ? 'T' : 'F'; + return count($resultsAdmin['WsGetMembershipsResults']['wsGroups'] ?? []) > 0 + || count($resultsUpdate['WsGetMembershipsResults']['wsGroups'] ?? []) > 0; } /** @@ -99,7 +104,7 @@ public function isGrouperVisible(string $userId, array $cfg): string $this->initApi($cfg); try { - $isMember = $this->grouperAPI->isMemberOfGrouperGroup(GrouperSpecialGroups::GROUPER_VISIBLE_GROUP, $userId); + $isMember = $this->grouperAPI->isMemberOfGroup(GrouperSpecialGroups::GROUPER_VISIBLE_GROUP, $userId); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; @@ -276,82 +281,41 @@ private function memberOfGroups(string $actorUserId, string $userId, array $cfg) $this->initApi($cfg); try { - return $this->grouperAPI->getUserGrouperGroupMemberships($actorUserId, $userId); + return $this->grouperAPI->getUserGroupMemberships($actorUserId, $userId); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } } - /** - * Process for User to Leave a Group + * Return all Grouper Groups that the User has a role of owner/admin * - * @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 + * @param array $conditions Listing of conditions for display of records, including UserId + * @param array $cfg * - * @return bool True|False + * @return array * @throws GrouperLiteWidgetException - * * @since COmanage Registry v4.4.0 */ - public function leaveGroup(string $userId, string $groupName, array $cfg): bool + public function getOwnedGroups(array $conditions, array $cfg): array { - $this->initApi($cfg); - - try { - return $this->grouperAPI->removeGrouperGroupMember($userId, - $groupName, - $userId // Remove myself from the Group - ); - } catch (Exception $e) { - CakeLog::write('error', __METHOD__ . ': An error occurred'); - throw $e; + if(empty($conditions['userId'])) { + return false; } - } - /** - * Process for User to Join a Group - * - * @param string $userId Id of User - * @param string $groupName Name of Group Joining, do not confuse with DisplayName field! - * @return bool True|False - * @throws GrouperLiteWidgetException - * - * @since COmanage Registry v4.4.0 - */ - public function joinGroup(string $userId, string $groupName, array $cfg): bool - { $this->initApi($cfg); try { - return $this->grouperAPI->addGrouperGroupMember($userId, $groupName, $userId); + $resultsAdmin = $this->grouperAPI->getGrouperUserMemberships($conditions['userId'], GrouperGroupTypeEnum::ADMIN); + $resultsUpdate = $this->grouperAPI->getGrouperUserMemberships($conditions['userId'], GrouperGroupTypeEnum::UPDATE); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; } - } - - /** - * Return all Grouper Groups that the User has a role of owner/admin - * - * @param array $conditions Listing of conditions for display of records, including UserId - * @return array - * @throws GrouperLiteWidgetException - * - * @since COmanage Registry v4.4.0 - */ - public function ownerGroups(array $conditions, array $cfg) - { - $this->initApi($cfg); - try { - return $this->grouperAPI->getOwnedGroups($conditions['userId']); - } catch (Exception $e) { - CakeLog::write('error', __METHOD__ . ': An error occurred'); - throw $e; - } + return $this->removeDuplicates($resultsAdmin['WsGetMembershipsResults']['wsGroups'] ?? [], + $resultsUpdate['WsGetMembershipsResults']['wsGroups'] ?? []); } /** @@ -368,12 +332,12 @@ public function ownerGroups(array $conditions, array $cfg) * @throws JsonException * @since COmanage Registry v4.4.0 */ - public function getGrouperGroupMembers(string $actorUserId, string $groupName, array $cfg): array + public function getGroupMembers(string $actorUserId, string $groupName, array $cfg): array { $this->initApi($cfg); try { - $groupMembers = $this->grouperAPI->getGrouperGroupMembers($actorUserId, $groupName); + $groupMembers = $this->grouperAPI->getGroupMembers($actorUserId, $groupName); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; @@ -406,16 +370,11 @@ public function getGrouperGroupMembers(string $actorUserId, string $groupName, a * @throws JsonException * @since COmanage Registry v4.4.0 */ - public function addGrouperGroupMember(string $actAsUserId, string $groupName, string $addUserId, array $cfg) + public function addGroupMember(string $actAsUserId, string $groupName, string $addUserId, array $cfg) { $this->initApi($cfg); - try { - return $this->grouperAPI->addGrouperGroupMember($actAsUserId, $groupName, $addUserId); - } catch (Exception $e) { - CakeLog::write('error', __METHOD__ . ': An error occurred'); - throw $e; - } + return $this->grouperAPI->addGroupMember($actAsUserId, $groupName, $addUserId); } /** @@ -431,18 +390,14 @@ public function addGrouperGroupMember(string $actAsUserId, string $groupName, st * @throws JsonException Captured in Controller * @since COmanage Registry v4.4.0 */ - public function removeGrouperGroupMember(string $userId, string $groupName, string $removeUserId, array $cfg): bool + public function removeGroupMember(string $userId, + string $groupName, + string $removeUserId, + array $cfg): bool { $this->initApi($cfg); - try { - return $this->grouperAPI->removeGrouperGroupMember($userId, - $groupName, - $removeUserId); - } catch (Exception $e) { - CakeLog::write('error', __METHOD__ . ': An error occurred'); - throw $e; - } + return $this->grouperAPI->removeGroupMember($userId, $groupName, $removeUserId); } @@ -457,7 +412,7 @@ public function removeGrouperGroupMember(string $userId, string $groupName, stri * * @since COmanage Registry v4.4.0 */ - public function optinGroups(array $conditions, array $cfg) + public function optinGroups(array $conditions, array $cfg): array { $this->initApi($cfg); @@ -465,18 +420,19 @@ public function optinGroups(array $conditions, array $cfg) // Groups the user can join or leave $joinOrLeave = $this->grouperAPI->getOptionalGroups($conditions['userId'], GrouperGroupTypeEnum::OPTINS); - // Groups the user is a member of - $userGroups = $this->memberOfGroups($conditions['userId'], - $conditions['userId'], - $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; } + + // Groups the user is a member of + $userGroups = $this->memberOfGroups($conditions['userId'], + $conditions['userId'], + $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)); } /** @@ -493,7 +449,7 @@ public function isTemplateUser(string $userId, array $cfg) $this->initApi($cfg); try { - $isMember = $this->grouperAPI->isMemberOfGrouperGroup(GrouperSpecialGroups::TEMPLATE_CREATION_GROUP, $userId); + $isMember = $this->grouperAPI->isMemberOfGroup(GrouperSpecialGroups::TEMPLATE_CREATION_GROUP, $userId); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; @@ -725,4 +681,36 @@ private function getFriendlyWorkingGroupName(array $groups, string $method) { return $finalWorkingGroups; } + + + /** + * 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; + } + + foreach ($arrL as $large) { + foreach ($arrS as $key => $val) { + if ($large['uuid'] == $val['uuid']) { + unset($arrS[$key]); + } + } + } + + return array_merge_recursive($arrL, $arrS); + } } diff --git a/View/GrouperGroups/index.ctp b/View/GrouperGroups/index.ctp index 822086c..b05e647 100644 --- a/View/GrouperGroups/index.ctp +++ b/View/GrouperGroups/index.ctp @@ -2,7 +2,6 @@ extend('/GrouperGroups/base'); ?> = $this->Html->script('GrouperLiteWidget.vue-router.js') ?> Html->addCrumb(_txt('pl.grouperlite.nav.memberships')); ?> -