diff --git a/Controller/GrouperGroupsController.php b/Controller/GrouperGroupsController.php index 659496b..675b1f0 100644 --- a/Controller/GrouperGroupsController.php +++ b/Controller/GrouperGroupsController.php @@ -160,9 +160,9 @@ public function groupSubscribers(): void // : $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)); @@ -184,31 +184,31 @@ 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 { $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()); + $this->GrouperGroup->addGroupMember($this->userId, + $groupName, + $addUserId, + $this->CoGrouperLiteWidget->getConfig()); } 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_OK); } /** @@ -255,10 +255,10 @@ public function removeSubscriber(): void : $groupName; try { - $resultRemove = $this->GrouperGroup->removeGrouperGroupMember($this->userId, - $groupNameFormatted, - $remUserId, - $this->CoGrouperLiteWidget->getConfig()); + $resultRemove = $this->GrouperGroup->removeGroupMember($this->userId, + $groupNameFormatted, + $remUserId, + $this->CoGrouperLiteWidget->getConfig()); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': ' . var_export($e->getMessage(), true)); } @@ -432,22 +432,22 @@ public function groupCreateTemplate() */ public function joinGroup(): void { + // 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 $name = urldecode($this->request->query['GroupName']); - $resultAdd = false; try { // Add myself - $resultAdd = $this->GrouperGroup->joinGroup($this->userId, - $name, - $this->CoGrouperLiteWidget->getConfig()); + $this->GrouperGroup->addGroupMember($this->userId, + $name, + $this->userId, + $this->CoGrouperLiteWidget->getConfig()); } 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_OK); } /** diff --git a/Lib/GrouperApiAccess.php b/Lib/GrouperApiAccess.php index c84e8de..867aafb 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. @@ -84,9 +81,9 @@ public function __construct(array $cfg) * @throws GrouperLiteWidgetException * @throws 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 +118,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 +264,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 +322,7 @@ public function deleteGroupWithTemplate(array $queryData) * @throws GrouperLiteWidgetException * @throws JsonException */ - public function getGrouperGroupInfo(string $groupName) + public function getGroupInfo(string $groupName): array { $groupInfo = []; @@ -371,7 +373,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 []; @@ -400,7 +402,7 @@ public function getUserGrouperGroupMemberships(string $actorUserId, string $user * @throws GrouperLiteWidgetException * @throws JsonException */ - public function getGrouperGroupMembers(string $actorUserId, string $groupName): array + public function getGroupMembers(string $actorUserId, string $groupName): array { //Build request logic $usersToShow = [ @@ -572,7 +574,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 []; @@ -636,9 +638,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) diff --git a/Lib/GrouperHTTPWrapper.php b/Lib/GrouperHTTPWrapper.php index 727ae79..e9c025a 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..c9799a6 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,29 @@ class GrouperSpecialGroups { } class GrouperResultCodesEnum { - const IS_MEMBER = 'IS_MEMBER'; - const SUCCESS = 'SUCCESS'; + 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 NO_SUCH_OBJECT = 'NO_SUCH_OBJECT'; + const PROBLEM_WITH_ASSIGNMENT = 'PROBLEM_WITH_ASSIGNMENT'; + const SUBJECT_NOT_FOUND = 'SUBJECT_NOT_FOUND'; + const SUCCESS = 'SUCCESS'; const SUCCESS_NO_INFO = 'SUCCESS_NO_INFO'; - const EXECUTE_FAILED = 'EXECUTE_FAILED'; +} + +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'; } \ No newline at end of file diff --git a/Model/GrouperGroup.php b/Model/GrouperGroup.php index 2b52896..3a7c9b9 100644 --- a/Model/GrouperGroup.php +++ b/Model/GrouperGroup.php @@ -99,7 +99,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,57 +276,7 @@ private function memberOfGroups(string $actorUserId, string $userId, array $cfg) $this->initApi($cfg); try { - return $this->grouperAPI->getUserGrouperGroupMemberships($actorUserId, $userId); - } catch (Exception $e) { - CakeLog::write('error', __METHOD__ . ': An error occurred'); - throw $e; - } - } - - - /** - * Process for User to Leave a Group - * - * @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 GrouperLiteWidgetException - * - * @since COmanage Registry v4.4.0 - */ - public function leaveGroup(string $userId, string $groupName, array $cfg): bool - { - $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; - } - } - - /** - * 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); + return $this->grouperAPI->getUserGroupMemberships($actorUserId, $userId); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; @@ -368,12 +318,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 +356,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,14 +376,17 @@ 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); + return $this->grouperAPI->removeGroupMember($userId, + $groupName, + $removeUserId); } catch (Exception $e) { CakeLog::write('error', __METHOD__ . ': An error occurred'); throw $e; @@ -457,7 +405,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 +413,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 +442,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; diff --git a/webroot/js/groups.js b/webroot/js/groups.js index 3fa6393..c4cf3a4 100644 --- a/webroot/js/groups.js +++ b/webroot/js/groups.js @@ -76,10 +76,8 @@ export default { group.loading = true; const resp = await fetch(`${this.api.join}?GroupName=${group.name}&GroupDisplayName=${group.displayName}`, { headers: { - "Accept": "application/json", - // 'Content-Type': 'application/x-www-form-urlencoded', - }, - method: "DELETE" + "Accept": "application/json" + } }); if (resp.ok) { this.loadGroups(this.apiPath, this.query, false); diff --git a/webroot/js/members.js b/webroot/js/members.js index c3634d5..959cb55 100644 --- a/webroot/js/members.js +++ b/webroot/js/members.js @@ -89,13 +89,12 @@ export default { const resp = await fetch(`${this.api.remove}?group=${(name)}&userId=${id}`, { method: "DELETE", headers: { - "Accept": "application/json", - // 'Content-Type': 'application/x-www-form-urlencoded', + "Accept": "application/json" } }); if (resp.ok) { this.subscribers = []; - this.loadGroupSubscribers(this.group); + await this.loadGroupSubscribers(this.group); generateFlash(`${label || subName} ${this.txt.removeSubscriberSuccess} ${(displayExtension)}`, 'success'); } else { this.disabled = [ ...this.disabled, id ]; @@ -115,15 +114,16 @@ export default { method: "POST", headers: { "Accept": "application/json", - // 'Content-Type': 'application/x-www-form-urlencoded', }, body: formData }); if (resp.ok) { - this.loadGroupSubscribers(this.group); + await this.loadGroupSubscribers(this.group); generateFlash(`${label} ${this.txt.addSubscriberSuccess} ${(displayExtension)}`, 'success'); } else { - generateFlash(this.txt.addSubscriberError, 'error'); + generateFlash(`${this.txt.addSubscriberError}`, 'error'); + let errorResponse = await resp.json(); + generateFlash(`Server Reported:${errorResponse.message}`, 'error'); } this.loading = false;