diff --git a/lib/Controller/BookmarkController.php b/lib/Controller/BookmarkController.php index 6a351a65a8..4b3b4b4bad 100644 --- a/lib/Controller/BookmarkController.php +++ b/lib/Controller/BookmarkController.php @@ -22,6 +22,7 @@ use OCA\Bookmarks\Db\TreeMapper; use OCA\Bookmarks\Exception\AlreadyExistsError; use OCA\Bookmarks\Exception\HtmlParseError; +use OCA\Bookmarks\Exception\UnauthenticatedError; use OCA\Bookmarks\Exception\UnauthorizedAccessError; use OCA\Bookmarks\Exception\UnsupportedOperation; use OCA\Bookmarks\Exception\UrlParseError; @@ -237,7 +238,7 @@ private function toExternalFolderId(int $internal): int { */ public function getSingleBookmark($id): JSONResponse { if (!Authorizer::hasPermission(Authorizer::PERM_READ, $this->authorizer->getPermissionsForBookmark($id, $this->request))) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + return new JSONResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_NOT_FOUND); } try { /** @@ -245,9 +246,9 @@ public function getSingleBookmark($id): JSONResponse { */ $bm = $this->bookmarkMapper->find($id); } catch (DoesNotExistException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Not found'], Http::STATUS_NOT_FOUND); + return new JSONResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_NOT_FOUND); } catch (MultipleObjectsReturnedException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Multiple objects found'], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_NOT_FOUND); } return new JSONResponse(['item' => $this->_returnBookmarkAsArray($bm), 'status' => 'success']); } @@ -319,7 +320,7 @@ public function getBookmarks( $this->authorizer->setCredentials($this->request); if ($this->authorizer->getUserId() === null && $this->authorizer->getToken() === null) { - $res = new DataResponse(['status' => 'error', 'data' => 'Please authenticate first'], Http::STATUS_UNAUTHORIZED); + $res = new DataResponse(['status' => 'error', 'data' => ['Please authenticate first']], Http::STATUS_UNAUTHORIZED); $res->addHeader('WWW-Authenticate', 'Basic realm="Nextcloud", charset="UTF-8"'); return $res; } @@ -369,7 +370,7 @@ public function getBookmarks( if ($folder !== null) { if (!Authorizer::hasPermission(Authorizer::PERM_READ, $this->authorizer->getPermissionsForFolder($folder, $this->request))) { - return new DataResponse(['status' => 'error', 'data' => ['Insufficient permissions']], Http::STATUS_FORBIDDEN); + return new DataResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_BAD_REQUEST); } try { /** @var Folder $folderEntity */ @@ -379,7 +380,7 @@ public function getBookmarks( // to theirs $userId = $folderEntity->getUserId(); } catch (DoesNotExistException | MultipleObjectsReturnedException $e) { - return new DataResponse(['status' => 'error', 'data' => 'Not found'], Http::STATUS_BAD_REQUEST); + return new DataResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_BAD_REQUEST); } $params->setFolder($this->toInternalFolderId($folder)); $params->setRecursive($recursive); @@ -389,13 +390,13 @@ public function getBookmarks( try { $result = $this->bookmarkMapper->findAll($userId, $params); } catch (UrlParseError $e) { - return new DataResponse(['status' => 'error', 'data' => 'Failed to parse URL'], Http::STATUS_BAD_REQUEST); + return new DataResponse(['status' => 'error', 'data' => ['Failed to parse URL']], Http::STATUS_BAD_REQUEST); } } else { try { $result = $this->bookmarkMapper->findAllInPublicFolder($this->authorizer->getToken(), $params); } catch (DoesNotExistException | MultipleObjectsReturnedException $e) { - return new DataResponse(['status' => 'error', 'data' => 'Not found'], Http::STATUS_BAD_REQUEST); + return new DataResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_BAD_REQUEST); } } @@ -430,7 +431,7 @@ public function newBookmark($url = '', $title = null, $description = null, $tags $permissions &= $this->authorizer->getPermissionsForFolder($folder, $this->request); } if (!Authorizer::hasPermission(Authorizer::PERM_WRITE, $permissions) || $this->authorizer->getUserId() === null) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + return new JSONResponse(['status' => 'error', 'data' => ['Could not add bookmark']], Http::STATUS_BAD_REQUEST); } try { @@ -441,17 +442,17 @@ public function newBookmark($url = '', $title = null, $description = null, $tags return new JSONResponse(['item' => $this->_returnBookmarkAsArray($bookmark), 'status' => 'success']); } catch (AlreadyExistsError $e) { // This is really unlikely, as we make sure to use the existing one if it already exists - return new JSONResponse(['status' => 'error', 'data' => 'Bookmark already exists'], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['Bookmark already exists']], Http::STATUS_BAD_REQUEST); } catch (UrlParseError $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Invalid URL'], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['Invalid URL']], Http::STATUS_BAD_REQUEST); } catch (UserLimitExceededError $e) { - return new JSONResponse(['status' => 'error', 'data' => 'User limit exceeded'], Http::STATUS_BAD_REQUEST); - } catch (DoesNotExistException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Could not add bookmark'], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['User limit exceeded']], Http::STATUS_BAD_REQUEST); + } catch (DoesNotExistException|\OCP\DB\Exception) { + return new JSONResponse(['status' => 'error', 'data' => ['Could not add bookmark']], Http::STATUS_BAD_REQUEST); } catch (MultipleObjectsReturnedException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Internal server error'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new JSONResponse(['status' => 'error', 'data' => ['Internal server error']], Http::STATUS_INTERNAL_SERVER_ERROR); } catch (UnsupportedOperation $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Internal server error'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new JSONResponse(['status' => 'error', 'data' => ['Internal server error']], Http::STATUS_INTERNAL_SERVER_ERROR); } } @@ -473,7 +474,7 @@ public function newBookmark($url = '', $title = null, $description = null, $tags */ public function editBookmark($id = null, $url = null, $title = null, $description = null, $tags = null, $folders = null, $target = null): JSONResponse { if (!Authorizer::hasPermission(Authorizer::PERM_EDIT, $this->authorizer->getPermissionsForBookmark($id, $this->request))) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + return new JSONResponse(['status' => 'error', 'data' => ['Could not edit bookmark']], Http::STATUS_NOT_FOUND); } try { @@ -493,18 +494,20 @@ public function editBookmark($id = null, $url = null, $title = null, $descriptio return new JSONResponse(['item' => $bookmark ? $this->_returnBookmarkAsArray($bookmark) : null, 'status' => 'success']); } catch (AlreadyExistsError $e) { // This is really unlikely, as we make sure to use the existing one if it already exists - return new JSONResponse(['status' => 'error', 'data' => 'Bookmark already exists'], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['Bookmark already exists']], Http::STATUS_BAD_REQUEST); } catch (UrlParseError $e) { $this->logger->error($e->getMessage(), ['exception' => $e]); - return new JSONResponse(['status' => 'error', 'data' => 'Invald URL'], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['Invald URL']], Http::STATUS_BAD_REQUEST); } catch (UserLimitExceededError $e) { - return new JSONResponse(['status' => 'error', 'data' => 'User limit exceeded'], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['User limit exceeded']], Http::STATUS_BAD_REQUEST); } catch (DoesNotExistException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Could not add bookmark'], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['Could not edit bookmark']], Http::STATUS_NOT_FOUND); + } catch (\OCP\DB\Exception $e) { + return new JSONResponse(['status' => 'error', 'data' => ['Could not edit bookmark']], Http::STATUS_BAD_REQUEST); } catch (MultipleObjectsReturnedException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Internal server error'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new JSONResponse(['status' => 'error', 'data' => ['Internal server error']], Http::STATUS_INTERNAL_SERVER_ERROR); } catch (UnsupportedOperation $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Could not add bookmark'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new JSONResponse(['status' => 'error', 'data' => ['Could not add bookmark']], Http::STATUS_INTERNAL_SERVER_ERROR); } } @@ -518,22 +521,23 @@ public function editBookmark($id = null, $url = null, $title = null, $descriptio * @PublicPage */ public function deleteBookmark($id): JSONResponse { + if (!Authorizer::hasPermission(Authorizer::PERM_EDIT, $this->authorizer->getPermissionsForBookmark($id, $this->request))) { + return new JSONResponse(['status' => 'success']); + } + try { $this->bookmarkMapper->find($id); - } catch (DoesNotExistException | MultipleObjectsReturnedException $e) { + } catch (DoesNotExistException | MultipleObjectsReturnedException) { return new JSONResponse(['status' => 'success']); } - if (!Authorizer::hasPermission(Authorizer::PERM_EDIT, $this->authorizer->getPermissionsForBookmark($id, $this->request))) { - return new JSONResponse(['status' => 'error', 'data' => ['Insufficient permissions']], Http::STATUS_FORBIDDEN); - } try { $this->bookmarks->delete($id); - } catch (UnsupportedOperation $e) { + } catch (UnsupportedOperation) { return new JSONResponse(['status' => 'error', 'data' => ['Unsupported operation']], Http::STATUS_INTERNAL_SERVER_ERROR); - } catch (DoesNotExistException $e) { + } catch (DoesNotExistException) { return new JSONResponse(['status' => 'success']); - } catch (MultipleObjectsReturnedException $e) { + } catch (MultipleObjectsReturnedException) { return new JSONResponse(['status' => 'error', 'data' => ['Multiple objects found']], Http::STATUS_INTERNAL_SERVER_ERROR); } return new JSONResponse(['status' => 'success']); @@ -552,7 +556,7 @@ public function deleteBookmark($id): JSONResponse { public function clickBookmark($url = ''): JSONResponse { $this->authorizer->setCredentials($this->request); if ($this->authorizer->getUserId() === null) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + return new JSONResponse(['status' => 'error', 'data' => ['Unauthenticated']], Http::STATUS_FORBIDDEN); } try { $bookmark = $this->bookmarks->findByUrl($this->authorizer->getUserId(), $url); @@ -587,16 +591,19 @@ public function clickBookmark($url = ''): JSONResponse { * @NoCSRFRequired * * @PublicPage - * @return DataDisplayResponse|NotFoundResponse|RedirectResponse|DataResponse + * @BruteForceProtection(action=bookmarks#getBookmarkImage) + * @return DataDisplayResponse|NotFoundResponse|RedirectResponse */ public function getBookmarkImage($id) { if (!Authorizer::hasPermission(Authorizer::PERM_READ, $this->authorizer->getPermissionsForBookmark($id, $this->request))) { - return new DataResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new NotFoundResponse(); + $res->throttle(); + return $res; } try { $image = $this->bookmarks->getImage($id); if ($image === null) { - return new DataResponse([], Http::STATUS_NOT_FOUND); + return new NotFoundResponse(); } return $this->doImageResponse($image); } catch (DoesNotExistException | MultipleObjectsReturnedException | Exception $e) { @@ -610,13 +617,15 @@ public function getBookmarkImage($id) { * * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#getBookmarkFavicon) * @PublicPage * @return DataDisplayResponse|NotFoundResponse|DataResponse */ public function getBookmarkFavicon($id) { if (!Authorizer::hasPermission(Authorizer::PERM_READ, $this->authorizer->getPermissionsForBookmark($id, $this->request))) { - return new DataResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new NotFoundResponse(); + $res->throttle(); + return $res; } try { $image = $this->bookmarks->getFavicon($id); @@ -661,12 +670,14 @@ public function doImageResponse(?IImage $image) { * * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#importBookmark) * @PublicPage */ public function importBookmark($folder = null): JSONResponse { if (!Authorizer::hasPermission(Authorizer::PERM_WRITE, $this->authorizer->getPermissionsForFolder($folder ?? -1, $this->request))) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new JSONResponse(['status' => 'error', 'data' => ['Folder not found']], Http::STATUS_BAD_REQUEST); + $res->throttle(); + return $res; } $full_input = $this->request->getUploadedFile('bm_import'); @@ -692,19 +703,21 @@ public function importBookmark($folder = null): JSONResponse { try { $result = $this->folders->importFile($this->authorizer->getUserId(), $file, $folder); } catch (UnauthorizedAccessError $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized access'], Http::STATUS_FORBIDDEN); + $res = new JSONResponse(['status' => 'error', 'data' => ['Folder not found']], Http::STATUS_BAD_REQUEST); + $res->throttle(); + return $res; } catch (DoesNotExistException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Folder not found'], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['Folder not found']], Http::STATUS_BAD_REQUEST); } catch (MultipleObjectsReturnedException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Multiple objects found'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new JSONResponse(['status' => 'error', 'data' => ['Multiple objects found']], Http::STATUS_INTERNAL_SERVER_ERROR); } catch (HtmlParseError $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Parse error: Invalid HTML'], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['Parse error: Invalid HTML']], Http::STATUS_BAD_REQUEST); } catch (UserLimitExceededError $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Could not import all bookmarks: User limit Exceeded'], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['Could not import all bookmarks: User limit Exceeded']], Http::STATUS_BAD_REQUEST); } catch (AlreadyExistsError $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Could not import all bookmarks: Already exists'], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['Could not import all bookmarks: Already exists']], Http::STATUS_BAD_REQUEST); } catch (UnsupportedOperation $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Internal server error'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new JSONResponse(['status' => 'error', 'data' => ['Internal server error']], Http::STATUS_INTERNAL_SERVER_ERROR); } if (count($result['errors']) !== 0) { $this->logger->warning(var_export($result['errors'], true), ['app' => 'bookmarks']); @@ -721,21 +734,26 @@ public function importBookmark($folder = null): JSONResponse { * @return ExportResponse|JSONResponse * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#exportBookmark) * @PublicPage */ public function exportBookmark() { $this->authorizer->setCredentials($this->request); + if ($this->authorizer->getUserId() === null) { + $res = new JSONResponse(['status' => 'error', 'data' => ['Not found']]); + $res->throttle(); + return $res; + } try { $data = $this->htmlExporter->exportFolder($this->authorizer->getUserId(), $this->_getRootFolderId()); } catch (UnauthorizedAccessError $e) { // Will probably never happen - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized access']); + return new JSONResponse(['status' => 'error', 'data' => ['Not found']]); } catch (DoesNotExistException $e) { // Neither will this - return new JSONResponse(['status' => 'error', 'data' => 'Not found']); + return new JSONResponse(['status' => 'error', 'data' => ['Not found']]); } catch (MultipleObjectsReturnedException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Multiple objects found']); + return new JSONResponse(['status' => 'error', 'data' => ['Multiple objects found']]); } return new ExportResponse($data); } @@ -746,12 +764,15 @@ public function exportBookmark() { * @return JSONResponse * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#countBookmarks) * @PublicPage + * @throws UnauthenticatedError */ public function countBookmarks(int $folder): JSONResponse { if (!Authorizer::hasPermission(Authorizer::PERM_READ, $this->authorizer->getPermissionsForFolder($folder, $this->request))) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new JSONResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_NOT_FOUND); + $res->throttle(); + return $res; } if ($folder === -1 && $this->authorizer->getUserId() !== null) { @@ -760,7 +781,12 @@ public function countBookmarks(int $folder): JSONResponse { } $folder = $this->toInternalFolderId($folder); - $count = $this->treeMapper->countBookmarksInFolder($this->toInternalFolderId($folder)); + if ($folder === null) { + $res = new JSONResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_NOT_FOUND); + $res->throttle(); + return $res; + } + $count = $this->treeMapper->countBookmarksInFolder($folder); return new JSONResponse(['status' => 'success', 'item' => $count]); } @@ -768,15 +794,22 @@ public function countBookmarks(int $folder): JSONResponse { * @return JSONResponse * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#countUnavailable) * @PublicPage + * @throws UnauthenticatedError */ public function countUnavailable(): JSONResponse { if (!Authorizer::hasPermission(Authorizer::PERM_READ, $this->authorizer->getPermissionsForFolder(-1, $this->request))) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new JSONResponse(['status' => 'error', 'data' => ['Unauthorized']], Http::STATUS_FORBIDDEN); + $res->throttle(); + return $res; } - $count = $this->bookmarkMapper->countUnavailable($this->authorizer->getUserId()); + try { + $count = $this->bookmarkMapper->countUnavailable($this->authorizer->getUserId()); + } catch (\OCP\DB\Exception $e) { + return new JSONResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_INTERNAL_SERVER_ERROR); + } return new JSONResponse(['status' => 'success', 'item' => $count]); } @@ -784,12 +817,15 @@ public function countUnavailable(): JSONResponse { * @return JSONResponse * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#countArchived) * @PublicPage + * @throws UnauthenticatedError */ public function countArchived(): JSONResponse { if (!Authorizer::hasPermission(Authorizer::PERM_READ, $this->authorizer->getPermissionsForFolder(-1, $this->request))) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new JSONResponse(['status' => 'error', 'data' => ['Unauthorized']], Http::STATUS_FORBIDDEN); + $res->throttle(); + return $res; } $count = $this->bookmarkMapper->countArchived($this->authorizer->getUserId()); @@ -800,12 +836,15 @@ public function countArchived(): JSONResponse { * @return JSONResponse * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#countDuplicated) * @PublicPage + * @throws UnauthenticatedError */ public function countDuplicated(): JSONResponse { if (!Authorizer::hasPermission(Authorizer::PERM_READ, $this->authorizer->getPermissionsForFolder(-1, $this->request))) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new JSONResponse(['status' => 'error', 'data' => ['Unauthorized']], Http::STATUS_FORBIDDEN); + $res->throttle(); + return $res; } $count = $this->bookmarkMapper->countDuplicated($this->authorizer->getUserId()); @@ -816,17 +855,20 @@ public function countDuplicated(): JSONResponse { * @return JSONResponse * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#acquireLock) * @PublicPage + * @throws UnauthenticatedError */ public function acquireLock(): JSONResponse { if (!Authorizer::hasPermission(Authorizer::PERM_WRITE, $this->authorizer->getPermissionsForFolder(-1, $this->request))) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new JSONResponse(['status' => 'error', 'data' => ['Unauthorized']], Http::STATUS_FORBIDDEN); + $res->throttle(); + return $res; } try { if ($this->lockManager->getLock($this->authorizer->getUserId()) === true) { - return new JSONResponse(['status' => 'error', 'data' => 'Resource is already locked'], Http::STATUS_LOCKED); + return new JSONResponse(['status' => 'error', 'data' => ['Resource is already locked']], Http::STATUS_LOCKED); } $this->lockManager->setLock($this->authorizer->getUserId(), true); @@ -841,12 +883,15 @@ public function acquireLock(): JSONResponse { * @return JSONResponse * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#releaseLock) * @PublicPage + * @throws UnauthenticatedError */ public function releaseLock(): JSONResponse { if (!Authorizer::hasPermission(Authorizer::PERM_WRITE, $this->authorizer->getPermissionsForFolder(-1, $this->request))) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new JSONResponse(['status' => 'error', 'data' => ['Unauthorized']], Http::STATUS_FORBIDDEN); + $res->throttle(); + return $res; } try { @@ -866,46 +911,66 @@ public function releaseLock(): JSONResponse { * @return Http\DataResponse * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#getDeletedBookmarks) * @PublicPage */ public function getDeletedBookmarks(): DataResponse { $this->authorizer->setCredentials($this->request); if ($this->authorizer->getUserId() === null) { - return new Http\DataResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new Http\DataResponse(['status' => 'error', 'data' => ['Unauthorized']], Http::STATUS_FORBIDDEN); + $res->throttle(); + return $res; } try { $bookmarks = $this->treeMapper->getSoftDeletedRootItems($this->authorizer->getUserId(), TreeMapper::TYPE_BOOKMARK); } catch (UrlParseError|\OCP\DB\Exception $e) { - return new Http\DataResponse(['status' => 'error', 'data' => 'Internal error'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new Http\DataResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_INTERNAL_SERVER_ERROR); } return new Http\DataResponse(['status' => 'success', 'data' => array_map(fn ($bookmark) => $this->_returnBookmarkAsArray($bookmark), $bookmarks)]); } + /** + * @return Http\DataResponse + * @NoAdminRequired + * @NoCSRFRequired + * @BruteForceProtection(action=bookmarks#countAllClicks) + * @return DataResponse + */ public function countAllClicks(): DataResponse { $this->authorizer->setCredentials($this->request); if ($this->authorizer->getUserId() === null) { - return new Http\DataResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new Http\DataResponse(['status' => 'error', 'data' => ['Unauthorized']], Http::STATUS_FORBIDDEN); + $res->throttle(); + return $res; } try { $count = $this->bookmarkMapper->countAllClicks($this->authorizer->getUserId()); return new DataResponse(['status' => 'success', 'item' => $count]); } catch (\OCP\DB\Exception $e) { - return new Http\DataResponse(['status' => 'error', 'data' => 'Internal error'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new Http\DataResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_INTERNAL_SERVER_ERROR); } } + /** + * @return Http\DataResponse + * @NoAdminRequired + * @NoCSRFRequired + * @BruteForceProtection(action=bookmarks#countWithClicks) + * @return DataResponse + */ public function countWithClicks(): DataResponse { $this->authorizer->setCredentials($this->request); if ($this->authorizer->getUserId() === null) { - return new Http\DataResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new Http\DataResponse(['status' => 'error', 'data' => ['Unauthorized']], Http::STATUS_FORBIDDEN); + $res->throttle(); + return $res; } try { $count = $this->bookmarkMapper->countWithClicks($this->authorizer->getUserId()); return new DataResponse(['status' => 'success', 'item' => $count]); } catch (\OCP\DB\Exception $e) { - return new Http\DataResponse(['status' => 'error', 'data' => 'Internal error'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new Http\DataResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_INTERNAL_SERVER_ERROR); } } } diff --git a/lib/Controller/FoldersController.php b/lib/Controller/FoldersController.php index fe5337026a..28c6f432d0 100644 --- a/lib/Controller/FoldersController.php +++ b/lib/Controller/FoldersController.php @@ -15,10 +15,12 @@ use OCA\Bookmarks\Db\SharedFolder; use OCA\Bookmarks\Db\ShareMapper; use OCA\Bookmarks\Db\TreeMapper; +use OCA\Bookmarks\Exception\AlreadyExistsError; use OCA\Bookmarks\Exception\ChildrenOrderValidationError; use OCA\Bookmarks\Exception\UnauthenticatedError; use OCA\Bookmarks\Exception\UnsupportedOperation; use OCA\Bookmarks\Exception\UrlParseError; +use OCA\Bookmarks\Exception\UserLimitExceededError; use OCA\Bookmarks\Service\Authorizer; use OCA\Bookmarks\Service\BookmarkService; use OCA\Bookmarks\Service\FolderService; @@ -131,9 +133,6 @@ private function _returnFolderAsArray($folder): array { return $returnFolder; } if ($folder instanceof SharedFolder) { - /** - * @var $share Share - */ $share = $this->shareMapper->findByFolderAndUser($folder->getFolderId(), $folder->getUserId()); $returnFolder = $folder->toArray(); $returnFolder['id'] = $folder->getFolderId(); @@ -154,22 +153,27 @@ private function _returnFolderAsArray($folder): array { * * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#addFolder) * @PublicPage + * @throws UnauthenticatedError */ - public function addFolder($title = '', $parent_folder = -1): JSONResponse { + public function addFolder(string $title = '', int $parent_folder = -1): JSONResponse { if (!Authorizer::hasPermission(Authorizer::PERM_WRITE, $this->authorizer->getPermissionsForFolder($parent_folder, $this->request))) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new JSONResponse(['status' => 'error', 'data' => ['Could not find parent folder']], Http::STATUS_BAD_REQUEST); + $res->throttle(); + return $res; } try { $parent_folder = $this->toInternalFolderId($parent_folder); $folder = $this->folders->create($title, $parent_folder); } catch (MultipleObjectsReturnedException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Multiple parent folders found'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new JSONResponse(['status' => 'error', 'data' => ['Multiple parent folders found']], Http::STATUS_INTERNAL_SERVER_ERROR); } catch (UnsupportedOperation $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Unsupported operation'], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['Unsupported operation']], Http::STATUS_BAD_REQUEST); } catch (DoesNotExistException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Could not find parent folder'], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['Could not find parent folder']], Http::STATUS_BAD_REQUEST); + } catch (Exception $e) { + return new JSONResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_INTERNAL_SERVER_ERROR); } return new JSONResponse(['status' => 'success', 'item' => $this->_returnFolderAsArray($folder)]); @@ -181,21 +185,26 @@ public function addFolder($title = '', $parent_folder = -1): JSONResponse { * * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#addFolder) * @PublicPage + * @throws UnauthenticatedError */ public function getFolder($folderId): JSONResponse { if (!Authorizer::hasPermission(Authorizer::PERM_READ, $this->authorizer->getPermissionsForFolder($folderId, $this->request))) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new JSONResponse(['status' => 'error', 'data' => ['Could not find folder']], Http::STATUS_BAD_REQUEST); + $res->throttle(); + return $res; } $folderId = $this->toInternalFolderId($folderId); try { $folder = $this->folders->findSharedFolderOrFolder($this->authorizer->getUserId(), $folderId); return new JSONResponse(['status' => 'success', 'item' => $this->_returnFolderAsArray($folder)]); } catch (DoesNotExistException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Could not find folder'], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['Could not find folder']], Http::STATUS_BAD_REQUEST); } catch (MultipleObjectsReturnedException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Multiple objects found'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new JSONResponse(['status' => 'error', 'data' => ['Multiple objects found']], Http::STATUS_INTERNAL_SERVER_ERROR); + } catch (UnsupportedOperation $e) { + return new JSONResponse(['status' => 'error', 'data' => 'Internal error'], Http::STATUS_INTERNAL_SERVER_ERROR); } } @@ -206,19 +215,32 @@ public function getFolder($folderId): JSONResponse { * * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#addToFolder) * @PublicPage + * @throws UnauthenticatedError */ public function addToFolder($folderId, $bookmarkId): JSONResponse { if (!Authorizer::hasPermission(Authorizer::PERM_WRITE, $this->authorizer->getPermissionsForFolder($folderId, $this->request)) && !Authorizer::hasPermission(Authorizer::PERM_EDIT, $this->authorizer->getPermissionsForBookmark($bookmarkId, $this->request))) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new JSONResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_NOT_FOUND); + $res->throttle(); + return $res; } $folderId = $this->toInternalFolderId($folderId); try { $this->bookmarks->addToFolder($folderId, $bookmarkId); } catch (UnsupportedOperation $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Unsupported operation'], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['Unsupported operation']], Http::STATUS_BAD_REQUEST); + } catch (AlreadyExistsError $e) { + // noop + } catch (UrlParseError $e) { + return new JSONResponse(['status' => 'error', 'data' => ['Malformed URL']], Http::STATUS_BAD_REQUEST); + } catch (UserLimitExceededError $e) { + return new JSONResponse(['status' => 'error', 'data' => ['User limit exceeded']], Http::STATUS_BAD_REQUEST); + } catch (DoesNotExistException $e) { + return new JSONResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_NOT_FOUND); + } catch (MultipleObjectsReturnedException|Exception $e) { + return new JSONResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_INTERNAL_SERVER_ERROR); } return new JSONResponse(['status' => 'success']); @@ -231,25 +253,28 @@ public function addToFolder($folderId, $bookmarkId): JSONResponse { * * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#removeFromFolder) * @PublicPage + * @throws UnauthenticatedError */ public function removeFromFolder($folderId, $bookmarkId, bool $hardDelete = false): JSONResponse { if (!Authorizer::hasPermission(Authorizer::PERM_WRITE, $this->authorizer->getPermissionsForFolder($folderId, $this->request)) || !Authorizer::hasPermission(Authorizer::PERM_EDIT, $this->authorizer->getPermissionsForBookmark($bookmarkId, $this->request))) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new JSONResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_BAD_REQUEST); + $res->throttle(); + return $res; } try { $folderId = $this->toInternalFolderId($folderId); $this->bookmarks->removeFromFolder($folderId, $bookmarkId, $hardDelete); } catch (DoesNotExistException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Could not find folder'], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_BAD_REQUEST); } catch (MultipleObjectsReturnedException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Multiple objects found'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new JSONResponse(['status' => 'error', 'data' => ['Multiple objects found']], Http::STATUS_INTERNAL_SERVER_ERROR); } catch (UnsupportedOperation $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Unsupported operation'], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['Unsupported operation']], Http::STATUS_BAD_REQUEST); } catch (Exception $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Internal error'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new JSONResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_INTERNAL_SERVER_ERROR); } return new JSONResponse(['status' => 'success']); @@ -262,32 +287,33 @@ public function removeFromFolder($folderId, $bookmarkId, bool $hardDelete = fals * * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(actions: 'bookmarks#undeleteFromFolder') * @PublicPage + * @throws UnauthenticatedError */ public function undeleteFromFolder(int $folderId, int $bookmarkId): JSONResponse { if (!Authorizer::hasPermission(Authorizer::PERM_WRITE, $this->authorizer->getPermissionsForFolder($folderId, $this->request)) || !Authorizer::hasPermission(Authorizer::PERM_EDIT, $this->authorizer->getPermissionsForBookmark($bookmarkId, $this->request))) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new JSONResponse(['status' => 'error', 'data' => ['Unauthorized']], Http::STATUS_FORBIDDEN); + $res->throttle(); + return $res; } try { $folderId = $this->toInternalFolderId($folderId); $this->bookmarks->undeleteInFolder($folderId, $bookmarkId); return new JSONResponse(['status' => 'success']); } catch (DoesNotExistException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Could not find folder'], Http::STATUS_NOT_FOUND); + return new JSONResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_NOT_FOUND); } catch (MultipleObjectsReturnedException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Multiple objects found'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new JSONResponse(['status' => 'error', 'data' => ['Multiple objects found']], Http::STATUS_INTERNAL_SERVER_ERROR); } catch (UnsupportedOperation $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Unsupported operation'], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['Unsupported operation']], Http::STATUS_BAD_REQUEST); } catch (Exception $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Internal error'], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_BAD_REQUEST); } } - - /** * @param int $folderId * @param bool $hardDelete @@ -297,10 +323,13 @@ public function undeleteFromFolder(int $folderId, int $bookmarkId): JSONResponse * @NoCSRFRequired * * @PublicPage + * @throws UnauthenticatedError */ public function deleteFolder(int $folderId, bool $hardDelete = false): JSONResponse { if (!Authorizer::hasPermission(Authorizer::PERM_EDIT, $this->authorizer->getPermissionsForFolder($folderId, $this->request))) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new JSONResponse(['status' => 'success']); + $res->throttle(); + return $res; } $folderId = $this->toInternalFolderId($folderId); @@ -311,11 +340,11 @@ public function deleteFolder(int $folderId, bool $hardDelete = false): JSONRespo $this->folders->deleteSharedFolderOrFolder($this->authorizer->getUserId(), $folderId, $hardDelete); return new JSONResponse(['status' => 'success']); } catch (UnsupportedOperation $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Unsupported operation'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new JSONResponse(['status' => 'error', 'data' => ['Unsupported operation']], Http::STATUS_INTERNAL_SERVER_ERROR); } catch (DoesNotExistException $e) { return new JSONResponse(['status' => 'success']); } catch (MultipleObjectsReturnedException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Multiple objects found'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new JSONResponse(['status' => 'error', 'data' => ['Multiple objects found']], Http::STATUS_INTERNAL_SERVER_ERROR); } } @@ -325,12 +354,15 @@ public function deleteFolder(int $folderId, bool $hardDelete = false): JSONRespo * * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#undeleteFolder) * @PublicPage + * @throws UnauthenticatedError */ public function undeleteFolder(int $folderId): JSONResponse { if (!Authorizer::hasPermission(Authorizer::PERM_EDIT, $this->authorizer->getPermissionsForFolder($folderId, $this->request))) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new JSONResponse(['status' => 'success']); + $res->throttle(); + return $res; } $folderId = $this->toInternalFolderId($folderId); @@ -338,13 +370,13 @@ public function undeleteFolder(int $folderId): JSONResponse { $this->folders->undelete($this->authorizer->getUserId(), $folderId); return new JSONResponse(['status' => 'success']); } catch (UnsupportedOperation $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Unsupported operation'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new JSONResponse(['status' => 'error', 'data' => ['Unsupported operation']], Http::STATUS_INTERNAL_SERVER_ERROR); } catch (DoesNotExistException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Could not find folder'], Http::STATUS_NOT_FOUND); + return new JSONResponse(['status' => 'error', 'data' => ['Could not find folder']], Http::STATUS_NOT_FOUND); } catch (MultipleObjectsReturnedException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Multiple objects found'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new JSONResponse(['status' => 'error', 'data' => ['Multiple objects found']], Http::STATUS_INTERNAL_SERVER_ERROR); } catch (Exception $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Internal error'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new JSONResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_INTERNAL_SERVER_ERROR); } } @@ -356,12 +388,15 @@ public function undeleteFolder(int $folderId): JSONResponse { * * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#editFolder) * @PupblicPage + * @throws UnauthenticatedError */ - public function editFolder($folderId, $title = null, $parent_folder = null): JSONResponse { + public function editFolder(int $folderId, ?string $title = null, ?int $parent_folder = null): JSONResponse { if (!Authorizer::hasPermission(Authorizer::PERM_EDIT, $this->authorizer->getPermissionsForFolder($folderId, $this->request))) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new JSONResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_NOT_FOUND); + $res->throttle(); + return $res; } if ($parent_folder !== null) { $parent_folder = $this->toInternalFolderId($parent_folder); @@ -370,13 +405,13 @@ public function editFolder($folderId, $title = null, $parent_folder = null): JSO $folder = $this->folders->updateSharedFolderOrFolder($this->authorizer->getUserId(), $folderId, $title, $parent_folder); return new JSONResponse(['status' => 'success', 'item' => $this->_returnFolderAsArray($folder)]); } catch (DoesNotExistException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Could not find folder'], Http::STATUS_BAD_REQUEST); - } catch (MultipleObjectsReturnedException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Multiple objects found'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new JSONResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_NOT_FOUND); + } catch (MultipleObjectsReturnedException|Exception $e) { + return new JSONResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_INTERNAL_SERVER_ERROR); } catch (UnsupportedOperation $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Unsupported operation: ' . $e->getMessage()], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['Unsupported operation']], Http::STATUS_BAD_REQUEST); } catch (UrlParseError $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Error changing owner of a bookmark: UrlParseError'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new JSONResponse(['status' => 'error', 'data' => ['Error changing owner of a bookmark: UrlParseError']], Http::STATUS_INTERNAL_SERVER_ERROR); } } @@ -387,12 +422,15 @@ public function editFolder($folderId, $title = null, $parent_folder = null): JSO * * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=hashFolder) * @PublicPage + * @throws UnauthenticatedError */ - public function hashFolder($folderId, $fields = ['title', 'url']): JSONResponse { + public function hashFolder(int $folderId, array $fields = ['title', 'url']): JSONResponse { if (!Authorizer::hasPermission(Authorizer::PERM_READ, $this->authorizer->getPermissionsForFolder($folderId, $this->request))) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new JSONResponse(['status' => 'error', 'data' => ['Could not find folder']], Http::STATUS_BAD_REQUEST); + $res->throttle(); + return $res; } try { $folderId = $this->toInternalFolderId($folderId); @@ -402,13 +440,13 @@ public function hashFolder($folderId, $fields = ['title', 'url']): JSONResponse $res->addHeader('Expires', 'Sat, 26 Jul 1997 05:00:00 GMT'); return $res; } catch (DoesNotExistException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Could not find folder'], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['Could not find folder']], Http::STATUS_BAD_REQUEST); } catch (MultipleObjectsReturnedException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Multiple objects found'], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['Multiple objects found']], Http::STATUS_BAD_REQUEST); } catch (\JsonException $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Could not find folder'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new JSONResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_INTERNAL_SERVER_ERROR); } catch (UnsupportedOperation $e) { - return new JSONResponse(['status' => 'error', 'data' => 'Unsupported operation: '.$e->getMessage()], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['Unsupported operation']], Http::STATUS_BAD_REQUEST); } } @@ -421,12 +459,18 @@ public function hashFolder($folderId, $fields = ['title', 'url']): JSONResponse * @NoCSRFRequired * * @PublicPage + * @throws UnauthenticatedError */ - public function getFolderChildren($folderId, $layers = 0): JSONResponse { + public function getFolderChildren(int $folderId, int $layers = 0): JSONResponse { if (!Authorizer::hasPermission(Authorizer::PERM_READ, $this->authorizer->getPermissionsForFolder($folderId, $this->request))) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new JSONResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_NOT_FOUND); + $res->throttle(); + return $res; } $folderId = $this->toInternalFolderId($folderId); + if ($folderId === null) { + return new JSONResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_NOT_FOUND); + } $children = $this->treeMapper->getChildren($folderId, $layers); $res = new JSONResponse(['status' => 'success', 'data' => $children]); $res->addHeader('Cache-Control', 'no-cache, must-revalidate'); @@ -441,15 +485,25 @@ public function getFolderChildren($folderId, $layers = 0): JSONResponse { * * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#getFolderChildrenOrder) * @PublicPage + * @throws UnauthenticatedError */ - public function getFolderChildrenOrder($folderId, $layers = 0): JSONResponse { + public function getFolderChildrenOrder(int $folderId, int $layers = 0): JSONResponse { if (!Authorizer::hasPermission(Authorizer::PERM_READ, $this->authorizer->getPermissionsForFolder($folderId, $this->request))) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new JSONResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_NOT_FOUND); + $res->throttle(); + return $res; } $folderId = $this->toInternalFolderId($folderId); - $children = $this->treeMapper->getChildrenOrder($folderId, $layers); + if ($folderId === null) { + return new JSONResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_NOT_FOUND); + } + try { + $children = $this->treeMapper->getChildrenOrder($folderId, $layers); + } catch (Exception $e) { + return new JSONResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_INTERNAL_SERVER_ERROR); + } $res = new JSONResponse(['status' => 'success', 'data' => $children]); $res->addHeader('Cache-Control', 'no-cache, must-revalidate'); $res->addHeader('Expires', 'Sat, 26 Jul 1997 05:00:00 GMT'); @@ -465,17 +519,23 @@ public function getFolderChildrenOrder($folderId, $layers = 0): JSONResponse { * @NoCSRFRequired * * @PublicPage + * @throws UnauthenticatedError */ - public function setFolderChildrenOrder($folderId, $data = []): JSONResponse { + public function setFolderChildrenOrder(int $folderId, array $data = []): JSONResponse { if (!Authorizer::hasPermission(Authorizer::PERM_WRITE, $this->authorizer->getPermissionsForFolder($folderId, $this->request))) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new JSONResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_NOT_FOUND); + $res->throttle(); + return $res; } $folderId = $this->toInternalFolderId($folderId); + if ($folderId === null) { + return new JSONResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_NOT_FOUND); + } try { $this->treeMapper->setChildrenOrder($folderId, $data); return new JSONResponse(['status' => 'success']); } catch (ChildrenOrderValidationError $e) { - return new JSONResponse(['status' => 'error', 'data' => 'invalid children order: ' . $e->getMessage()], Http::STATUS_BAD_REQUEST); + return new JSONResponse(['status' => 'error', 'data' => ['invalid children order: ' . $e->getMessage()]], Http::STATUS_BAD_REQUEST); } } @@ -485,15 +545,21 @@ public function setFolderChildrenOrder($folderId, $data = []): JSONResponse { * * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#getFolders) * @PublicPage * @return JSONResponse + * @throws UnauthenticatedError */ - public function getFolders($root = -1, $layers = -1): JSONResponse { + public function getFolders(int $root = -1, int $layers = -1): JSONResponse { if (!Authorizer::hasPermission(Authorizer::PERM_READ, $this->authorizer->getPermissionsForFolder($root, $this->request))) { - return new JSONResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new JSONResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_NOT_FOUND); + $res->throttle(); + return $res; } $internalRoot = $this->toInternalFolderId($root); + if ($internalRoot === null) { + return new JSONResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_NOT_FOUND); + } $folders = $this->treeMapper->getSubFolders($internalRoot, $layers, $root === -1 ? false : null); if ($root === -1 || $root === '-1') { foreach ($folders as &$folder) { @@ -511,19 +577,22 @@ public function getFolders($root = -1, $layers = -1): JSONResponse { * @return Http\DataResponse * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#getFolderPublicToken) * @PublicPage + * @throws UnauthenticatedError */ - public function getFolderPublicToken($folderId): DataResponse { + public function getFolderPublicToken(int $folderId): DataResponse { if (!Authorizer::hasPermission(Authorizer::PERM_RESHARE, $this->authorizer->getPermissionsForFolder($folderId, $this->request))) { - return new Http\DataResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new Http\DataResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_NOT_FOUND); + $res->throttle(); + return $res; } try { $publicFolder = $this->publicFolderMapper->findByFolder($folderId); } catch (DoesNotExistException $e) { - return new Http\DataResponse(['status' => 'error', 'data' => 'Could not find folder'], Http::STATUS_NOT_FOUND); + return new Http\DataResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_NOT_FOUND); } catch (MultipleObjectsReturnedException $e) { - return new Http\DataResponse(['status' => 'error', 'data' => 'Multiple objects returned'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new Http\DataResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_INTERNAL_SERVER_ERROR); } return new Http\DataResponse(['status' => 'success', 'item' => $publicFolder->getId()]); } @@ -533,20 +602,23 @@ public function getFolderPublicToken($folderId): DataResponse { * @return Http\DataResponse * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#createFolderPublicToken) * @PublicPage + * @throws UnauthenticatedError */ - public function createFolderPublicToken($folderId): DataResponse { + public function createFolderPublicToken(int $folderId): DataResponse { if (!Authorizer::hasPermission(Authorizer::PERM_RESHARE, $this->authorizer->getPermissionsForFolder($folderId, $this->request))) { - return new Http\DataResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new Http\DataResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_NOT_FOUND); + $res->throttle(); + return $res; } try { $token = $this->folders->createFolderPublicToken($folderId); return new Http\DataResponse(['status' => 'success', 'item' => $token]); } catch (MultipleObjectsReturnedException $e) { - return new DataResponse(['status' => 'error', 'data' => 'Multiple objects returned'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new DataResponse(['status' => 'error', 'data' => ['Multiple objects returned']], Http::STATUS_INTERNAL_SERVER_ERROR); } catch (DoesNotExistException $e) { - return new DataResponse(['status' => 'error', 'data' => 'Could not find folder'], Http::STATUS_BAD_REQUEST); + return new DataResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_BAD_REQUEST); } } @@ -555,20 +627,23 @@ public function createFolderPublicToken($folderId): DataResponse { * @return Http\DataResponse * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#deleteFolderPublicToken) * @PublicPage + * @throws UnauthenticatedError */ - public function deleteFolderPublicToken($folderId): DataResponse { + public function deleteFolderPublicToken(int $folderId): DataResponse { if (!Authorizer::hasPermission(Authorizer::PERM_RESHARE, $this->authorizer->getPermissionsForFolder($folderId, $this->request))) { - return new Http\DataResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new DataResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_BAD_REQUEST); + $res->throttle(); + return $res; } try { $this->folders->deleteFolderPublicToken($folderId); return new Http\DataResponse(['status' => 'success']); } catch (DoesNotExistException $e) { - return new Http\DataResponse(['status' => 'success']); - } catch (MultipleObjectsReturnedException $e) { - return new Http\DataResponse(['status' => 'error', 'data' => 'Internal error'], Http::STATUS_BAD_REQUEST); + return new DataResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_BAD_REQUEST); + } catch (MultipleObjectsReturnedException|Exception $e) { + return new Http\DataResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_BAD_REQUEST); } } @@ -577,19 +652,24 @@ public function deleteFolderPublicToken($folderId): DataResponse { * @return Http\DataResponse * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#getShare) * @PublicPage + * @throws UnauthenticatedError */ public function getShare($shareId): DataResponse { try { $share = $this->shareMapper->find($shareId); } catch (DoesNotExistException $e) { - return new Http\DataResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_BAD_REQUEST); + $res = new Http\DataResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_NOT_FOUND); + $res->throttle(); + return $res; } catch (MultipleObjectsReturnedException $e) { - return new Http\DataResponse(['status' => 'error', 'data' => ['Mutliple objects found']], Http::STATUS_INTERNAL_SERVER_ERROR); + return new Http\DataResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_INTERNAL_SERVER_ERROR); } if (!Authorizer::hasPermission(Authorizer::PERM_READ, $this->authorizer->getPermissionsForFolder($share->getFolderId(), $this->request))) { - return new Http\DataResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new Http\DataResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_NOT_FOUND); + $res->throttle(); + return $res; } return new Http\DataResponse(['status' => 'success', 'item' => $share->toArray()]); } @@ -598,17 +678,23 @@ public function getShare($shareId): DataResponse { * @return Http\DataResponse * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#findSharedFolders)) * @PublicPage * @throws UnauthenticatedError */ public function findSharedFolders(): DataResponse { $permissions = $this->authorizer->getPermissionsForFolder(-1, $this->request); if (!Authorizer::hasPermission(Authorizer::PERM_READ, $permissions)) { - return new Http\DataResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new Http\DataResponse(['status' => 'error', 'data' => ['Unauthorized']], Http::STATUS_FORBIDDEN); + $res->throttle(); + return $res; } - $shares = $this->shareMapper->findByUser($this->authorizer->getUserId()); + try { + $shares = $this->shareMapper->findByUser($this->authorizer->getUserId()); + } catch (Exception $e) { + return new Http\DataResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_INTERNAL_SERVER_ERROR); + } return new Http\DataResponse(['status' => 'success', 'data' => array_map(function (Share $share) { return [ 'id' => $share->getFolderId(), @@ -620,17 +706,23 @@ public function findSharedFolders(): DataResponse { * @return Http\DataResponse * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#findShares) * @PublicPage * @throws UnauthenticatedError */ public function findShares(): DataResponse { $permissions = $this->authorizer->getPermissionsForFolder(-1, $this->request); if (Authorizer::hasPermission(Authorizer::PERM_READ, $permissions)) { - return new Http\DataResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new Http\DataResponse(['status' => 'error', 'data' => ['Unauthorized']], Http::STATUS_FORBIDDEN); + $res->throttle(); + return $res; } - $shares = $this->shareMapper->findByOwner($this->authorizer->getUserId()); + try { + $shares = $this->shareMapper->findByOwner($this->authorizer->getUserId()); + } catch (Exception $e) { + return new Http\DataResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_INTERNAL_SERVER_ERROR); + } return new Http\DataResponse(['status' => 'success', 'data' => array_map(function ($share) { return $share->toArray(); }, $shares)]); @@ -641,19 +733,21 @@ public function findShares(): DataResponse { * @return Http\DataResponse * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#getShares) * @PublicPage * @throws UnauthenticatedError */ - public function getShares($folderId): DataResponse { + public function getShares(int $folderId): DataResponse { $permissions = $this->authorizer->getPermissionsForFolder($folderId, $this->request); if (Authorizer::hasPermission(Authorizer::PERM_RESHARE, $permissions)) { try { $this->folderMapper->find($folderId); } catch (MultipleObjectsReturnedException $e) { - return new DataResponse(['status' => 'error', 'data' => 'Could not find folder'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new DataResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_INTERNAL_SERVER_ERROR); } catch (DoesNotExistException $e) { - return new DataResponse(['status' => 'error', 'data' => 'Could not find folder'], Http::STATUS_BAD_REQUEST); + $res = new DataResponse(['status' => 'error', 'data' => ['Could not find folder']], Http::STATUS_NOT_FOUND); + $res->throttle(); + return $res; } $shares = $this->shareMapper->findByFolder($folderId); return new Http\DataResponse(['status' => 'success', 'data' => array_map(static function (Share $share) { @@ -665,13 +759,17 @@ public function getShares($folderId): DataResponse { $this->folderMapper->find($folderId); $share = $this->shareMapper->findByFolderAndUser($folderId, $this->authorizer->getUserId()); } catch (MultipleObjectsReturnedException $e) { - return new DataResponse(['status' => 'error', 'data' => 'Could not find folder'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new DataResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_INTERNAL_SERVER_ERROR); } catch (DoesNotExistException $e) { - return new DataResponse(['status' => 'error', 'data' => 'Could not find folder'], Http::STATUS_BAD_REQUEST); + $res = new DataResponse(['status' => 'error', 'data' => ['Could not find folder']], Http::STATUS_NOT_FOUND); + $res->throttle(); + return $res; } - return new Http\DataResponse(['status' => 'success', 'data' => [$share->toArray()]]); + return new Http\DataResponse(['status' => 'success', 'data' => $share->toArray()]); } - return new Http\DataResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new DataResponse(['status' => 'error', 'data' => ['Could not find folder']], Http::STATUS_NOT_FOUND); + $res->throttle(); + return $res; } /** @@ -683,22 +781,25 @@ public function getShares($folderId): DataResponse { * @return Http\DataResponse * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#createShare) * @PublicPage + * @throws UnauthenticatedError */ - public function createShare($folderId, $participant, int $type, $canWrite = false, $canShare = false): DataResponse { + public function createShare(int $folderId, string $participant, int $type, bool $canWrite = false, bool $canShare = false): DataResponse { $permissions = $this->authorizer->getPermissionsForFolder($folderId, $this->request); if (!Authorizer::hasPermission(Authorizer::PERM_RESHARE, $permissions)) { - return new Http\DataResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new Http\DataResponse(['status' => 'error', 'data' => ['Could not find folder']], Http::STATUS_BAD_REQUEST); + $res->throttle(); + return $res; } try { $canWrite = $canWrite && Authorizer::hasPermission(Authorizer::PERM_WRITE, $permissions); $share = $this->folders->createShare($folderId, $participant, $type, $canWrite, $canShare); return new Http\DataResponse(['status' => 'success', 'item' => $share->toArray()]); } catch (DoesNotExistException $e) { - return new Http\DataResponse(['status' => 'error', 'data' => 'Could not find folder'], Http::STATUS_BAD_REQUEST); + return new Http\DataResponse(['status' => 'error', 'data' => ['Could not find folder']], Http::STATUS_BAD_REQUEST); } catch (MultipleObjectsReturnedException $e) { - return new Http\DataResponse(['status' => 'error', 'data' => 'Multiple objects returned'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new Http\DataResponse(['status' => 'error', 'data' => ['Multiple objects returned']], Http::STATUS_INTERNAL_SERVER_ERROR); } catch (UnsupportedOperation $e) { return new Http\DataResponse(['status' => 'error', 'data' => $e->getMessage()], Http::STATUS_BAD_REQUEST); } @@ -711,26 +812,34 @@ public function createShare($folderId, $participant, int $type, $canWrite = fals * @return Http\DataResponse * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#editShare) * @PublicPage */ - public function editShare($shareId, $canWrite = false, $canShare = false): Http\DataResponse { + public function editShare(int $shareId, bool $canWrite = false, bool $canShare = false): Http\DataResponse { try { $share = $this->shareMapper->find($shareId); } catch (DoesNotExistException $e) { - return new Http\DataResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_BAD_REQUEST); + $res = new Http\DataResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_BAD_REQUEST); + $res->throttle(); + return $res; } catch (MultipleObjectsReturnedException $e) { - return new Http\DataResponse(['status' => 'error', 'data' => ['Multiple objects found']], Http::STATUS_INTERNAL_SERVER_ERROR); + return new Http\DataResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_INTERNAL_SERVER_ERROR); } $permissions = $this->authorizer->getPermissionsForFolder($share->getFolderId(), $this->request); if (!Authorizer::hasPermission(Authorizer::PERM_RESHARE, $permissions)) { - return new Http\DataResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new Http\DataResponse(['status' => 'error', 'data' => ['Not found']], Http::STATUS_BAD_REQUEST); + $res->throttle(); + return $res; } $canWrite = $canWrite && Authorizer::hasPermission(Authorizer::PERM_WRITE, $permissions); $share->setCanWrite($canWrite); $share->setCanShare($canShare); - $this->shareMapper->update($share); + try { + $this->shareMapper->update($share); + } catch (Exception $e) { + return new Http\DataResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_INTERNAL_SERVER_ERROR); + } return new Http\DataResponse(['status' => 'success', 'item' => $share->toArray()]); } @@ -740,25 +849,28 @@ public function editShare($shareId, $canWrite = false, $canShare = false): Http\ * @return Http\DataResponse * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#deleteShare) * @PublicPage + * @throws UnauthenticatedError */ - public function deleteShare($shareId): DataResponse { + public function deleteShare(int $shareId): DataResponse { try { $share = $this->shareMapper->find($shareId); } catch (DoesNotExistException $e) { return new Http\DataResponse(['status' => 'success']); } catch (MultipleObjectsReturnedException $e) { - return new Http\DataResponse(['status' => 'error', 'data' => ['Multiple objects found']], Http::STATUS_INTERNAL_SERVER_ERROR); + return new Http\DataResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_INTERNAL_SERVER_ERROR); } if (!Authorizer::hasPermission(Authorizer::PERM_RESHARE, $this->authorizer->getPermissionsForFolder($share->getFolderId(), $this->request))) { - return new Http\DataResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new Http\DataResponse(['status' => 'success']); + $res->throttle(); + return $res; } try { $this->folders->deleteShare($shareId); - } catch (UnsupportedOperation | DoesNotExistException | MultipleObjectsReturnedException $e) { - return new Http\DataResponse(['status' => 'error', 'data' => 'Unsupported operation'], Http::STATUS_INTERNAL_SERVER_ERROR); + } catch (UnsupportedOperation | DoesNotExistException | MultipleObjectsReturnedException | Exception $e) { + return new Http\DataResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_INTERNAL_SERVER_ERROR); } return new Http\DataResponse(['status' => 'success']); } @@ -767,13 +879,15 @@ public function deleteShare($shareId): DataResponse { * @return Http\DataResponse * @NoAdminRequired * @NoCSRFRequired - * + * @BruteForceProtection(action=bookmarks#getDeletedFolders) * @PublicPage */ public function getDeletedFolders(): DataResponse { $this->authorizer->setCredentials($this->request); if ($this->authorizer->getUserId() === null) { - return new Http\DataResponse(['status' => 'error', 'data' => 'Unauthorized'], Http::STATUS_FORBIDDEN); + $res = new Http\DataResponse(['status' => 'error', 'data' => ['Unauthorized']], Http::STATUS_FORBIDDEN); + $res->throttle(); + return $res; } try { $folders = $this->treeMapper->getSoftDeletedRootItems($this->authorizer->getUserId(), TreeMapper::TYPE_FOLDER); @@ -783,7 +897,7 @@ public function getDeletedFolders(): DataResponse { return $array; }, $folders); } catch (UrlParseError|Exception $e) { - return new Http\DataResponse(['status' => 'error', 'data' => 'Internal error'], Http::STATUS_INTERNAL_SERVER_ERROR); + return new Http\DataResponse(['status' => 'error', 'data' => ['Internal error']], Http::STATUS_INTERNAL_SERVER_ERROR); } return new Http\DataResponse(['status' => 'success', 'data' => $folderItems]); diff --git a/lib/Controller/WebViewController.php b/lib/Controller/WebViewController.php index baef21d36f..b8dadc904d 100644 --- a/lib/Controller/WebViewController.php +++ b/lib/Controller/WebViewController.php @@ -105,9 +105,8 @@ public function index(): AugmentedTemplateResponse { * @return NotFoundResponse|PublicTemplateResponse * * @NoAdminRequired - * * @NoCSRFRequired - * + * @BruteForceProtection(action=link) * @PublicPage */ public function link(string $token) { @@ -127,10 +126,10 @@ public function link(string $token) { if ($user !== null) { $userName = $user->getDisplayName(); } - } catch (DoesNotExistException $e) { - return new NotFoundResponse(); - } catch (MultipleObjectsReturnedException $e) { - return new NotFoundResponse(); + } catch (DoesNotExistException|MultipleObjectsReturnedException $e) { + $res = new NotFoundResponse(); + $res->throttle(); + return $res; } $res = new PublicTemplateResponse($this->appName, 'main', []); diff --git a/lib/Db/BookmarkMapper.php b/lib/Db/BookmarkMapper.php index 88a92ca147..50f7f94874 100644 --- a/lib/Db/BookmarkMapper.php +++ b/lib/Db/BookmarkMapper.php @@ -192,12 +192,11 @@ public function find(int $id): Bookmark { /** * @param string $userId * @param QueryParameters $queryParams - * + * @param bool $withGroupBy * @return Bookmark[] * - * @throws UrlParseError - * @throws \OC\DB\Exceptions\DbalException * @throws Exception + * @throws UrlParseError */ public function findAll(string $userId, QueryParameters $queryParams, bool $withGroupBy = true): array { $rootFolder = $this->folderMapper->findRootFolder($userId); diff --git a/lib/Db/ShareMapper.php b/lib/Db/ShareMapper.php index 16f2f63f7b..13f1983653 100644 --- a/lib/Db/ShareMapper.php +++ b/lib/Db/ShareMapper.php @@ -68,6 +68,7 @@ public function findByFolder(int $folderId): array { * @return Share[] * * @psalm-return list + * @throws Exception */ public function findByOwner(string $userId): array { $qb = $this->db->getQueryBuilder(); diff --git a/lib/Middleware/ExceptionMiddleware.php b/lib/Middleware/ExceptionMiddleware.php index c96f8cd602..a17d316ee7 100644 --- a/lib/Middleware/ExceptionMiddleware.php +++ b/lib/Middleware/ExceptionMiddleware.php @@ -20,7 +20,8 @@ class ExceptionMiddleware extends Middleware { public function afterException($controller, $methodName, \Exception $exception): DataResponse { if ($controller instanceof BookmarkController || $controller instanceof InternalBookmarkController || $controller instanceof FoldersController || $controller instanceof InternalFoldersController) { if ($exception instanceof UnauthenticatedError) { - $res = new DataResponse(['status' => 'error', 'data' => 'Please authenticate first'], Http::STATUS_UNAUTHORIZED); + $res = new DataResponse(['status' => 'error', 'data' => ['Please authenticate first']], Http::STATUS_UNAUTHORIZED); + $res->throttle(); $res->addHeader('WWW-Authenticate', 'Basic realm="Nextcloud Bookmarks", charset="UTF-8"'); return $res; } diff --git a/lib/Service/BookmarkService.php b/lib/Service/BookmarkService.php index 22577dea56..00541ad4e7 100644 --- a/lib/Service/BookmarkService.php +++ b/lib/Service/BookmarkService.php @@ -131,6 +131,7 @@ public function __construct(BookmarkMapper $bookmarkMapper, FolderMapper $folder * @throws UnsupportedOperation * @throws UrlParseError * @throws UserLimitExceededError + * @throws Exception */ public function create(string $userId, string $url = '', ?string $title = null, ?string $description = null, ?array $tags = null, $folders = []): Bookmark { $bookmark = null; @@ -163,19 +164,21 @@ public function create(string $userId, string $url = '', ?string $title = null, } /** - * @param $title - * @param $url - * @param $description * @param $userId - * @param $tags - * @param $folders + * @param $url + * @param string|null $title + * @param string|null $description + * @param array|null $tags + * @param array $folders * @return Bookmark * @throws AlreadyExistsError + * @throws Exception + * @throws MultipleObjectsReturnedException + * @throws UnsupportedOperation * @throws UrlParseError * @throws UserLimitExceededError - * @throws UnsupportedOperation */ - private function _addBookmark($userId, $url, ?string $title = null, $description = null, ?array $tags = null, array $folders = []): Bookmark { + private function _addBookmark($userId, $url, ?string $title = null, ?string $description = null, ?array $tags = null, array $folders = []): Bookmark { $bookmark = null; try { @@ -268,8 +271,9 @@ private function _addBookmark($userId, $url, ?string $title = null, $description * @throws UnsupportedOperation * @throws UrlParseError * @throws UserLimitExceededError + * @throws Exception */ - public function update(string $userId, $id, ?string $url = null, ?string $title = null, ?string $description = null, ?array $tags = null, ?array $folders = null): ?Bookmark { + public function update(string $userId, int $id, ?string $url = null, ?string $title = null, ?string $description = null, ?array $tags = null, ?array $folders = null): ?Bookmark { /** * @var $bookmark Bookmark */ @@ -395,15 +399,10 @@ public function removeFromFolder(int $folderId, int $bookmarkId, bool $hardDelet * @throws UnsupportedOperation * @throws UrlParseError * @throws UserLimitExceededError + * @throws Exception */ public function addToFolder(int $folderId, int $bookmarkId): void { - /** - * @var $folder Folder - */ $folder = $this->folderMapper->find($folderId); - /** - * @var $bookmark Bookmark - */ $bookmark = $this->bookmarkMapper->find($bookmarkId); if ($folder->getUserId() === $bookmark->getUserId()) { $this->treeMapper->addToFolders(TreeMapper::TYPE_BOOKMARK, $bookmarkId, [$folderId]); @@ -454,17 +453,17 @@ public function findById(int $id) : ?Bookmark { } /** - * @param $userId - * @param string $url * @param string $userId * + * @param string $url * @return Bookmark * - * @throws DoesNotExistException|UrlParseError + * @throws DoesNotExistException + * @throws Exception + * @throws UrlParseError */ public function findByUrl(string $userId, string $url = ''): Bookmark { $params = new QueryParameters(); - /** @var Bookmark[] $bookmarks */ $bookmarks = $this->bookmarkMapper->findAll($userId, $params->setUrl($url)); if (isset($bookmarks[0])) { return $bookmarks[0]; @@ -480,7 +479,6 @@ public function findByUrl(string $userId, string $url = ''): Bookmark { * @throws UrlParseError */ public function click(int $id): void { - /** @var Bookmark $bookmark */ $bookmark = $this->bookmarkMapper->find($id); $bookmark->incrementClickcount(); $this->bookmarkMapper->update($bookmark); @@ -493,9 +491,6 @@ public function click(int $id): void { * @throws MultipleObjectsReturnedException */ public function getImage(int $id): ?IImage { - /** - * @var $bookmark Bookmark - */ $bookmark = $this->bookmarkMapper->find($id); return $this->bookmarkPreviewer->getImage($bookmark, true); } @@ -507,9 +502,6 @@ public function getImage(int $id): ?IImage { * @throws MultipleObjectsReturnedException */ public function getFavicon(int $id): ?IImage { - /** - * @var $bookmark Bookmark - */ $bookmark = $this->bookmarkMapper->find($id); return $this->faviconPreviewer->getImage($bookmark, true); } diff --git a/lib/Service/FolderService.php b/lib/Service/FolderService.php index 1202fc907d..450df01ddd 100644 --- a/lib/Service/FolderService.php +++ b/lib/Service/FolderService.php @@ -76,11 +76,9 @@ public function findById(int $id) : Folder { * @throws DoesNotExistException * @throws MultipleObjectsReturnedException * @throws UnsupportedOperation + * @throws Exception */ public function create($title, $parentFolderId): Folder { - /** - * @var $parentFolder Folder - */ $parentFolder = $this->folderMapper->find($parentFolderId); $folder = new Folder(); $folder->setTitle($title); @@ -89,7 +87,7 @@ public function create($title, $parentFolderId): Folder { $this->folderMapper->insert($folder); $this->treeMapper->move(TreeMapper::TYPE_FOLDER, $folder->getId(), $parentFolderId); - $this->eventDispatcher->dispatch(CreateEvent::class, new CreateEvent(TreeMapper::TYPE_FOLDER, $folder->getId())); + $this->eventDispatcher->dispatchTyped(new CreateEvent(TreeMapper::TYPE_FOLDER, $folder->getId())); return $folder; } @@ -115,16 +113,13 @@ public function findShareByDescendantAndUser(Folder $folder, $userId): ?Share { * @throws DoesNotExistException * @throws MultipleObjectsReturnedException */ - public function findSharedFolderOrFolder($userId, $folderId) { + public function findSharedFolderOrFolder($userId, $folderId): Folder|SharedFolder { $folder = $this->folderMapper->find($folderId); if ($userId === null || $userId === $folder->getUserId()) { return $folder; } try { - /** - * @var $sharedFolder SharedFolder - */ $sharedFolder = $this->sharedFolderMapper->findByFolderAndUser($folder->getId(), $userId); return $sharedFolder; } catch (DoesNotExistException $e) { @@ -142,7 +137,7 @@ public function findSharedFolderOrFolder($userId, $folderId) { * @throws UnsupportedOperation * @throws Exception */ - public function deleteSharedFolderOrFolder(string $userId, int $folderId, bool $hardDelete): void { + public function deleteSharedFolderOrFolder(?string $userId, int $folderId, bool $hardDelete): void { $folder = $this->folderMapper->find($folderId); if ($userId === null || $userId === $folder->getUserId()) { @@ -181,6 +176,7 @@ public function deleteSharedFolderOrFolder(string $userId, int $folderId, bool $ * @throws DoesNotExistException * @throws MultipleObjectsReturnedException * @throws UnsupportedOperation + * @throws Exception */ public function deleteShare($shareId): void { $this->treeMapper->deleteShare($shareId); @@ -212,7 +208,7 @@ public function undelete(?string $userId, int $folderId): void { } /** - * @param string $userId + * @param string|null $userId * @param int $folderId * @param string $title * @param int $parent_folder @@ -221,11 +217,9 @@ public function undelete(?string $userId, int $folderId): void { * @throws MultipleObjectsReturnedException * @throws UnsupportedOperation * @throws \OCA\Bookmarks\Exception\UrlParseError + * @throws Exception */ - public function updateSharedFolderOrFolder($userId, $folderId, $title = null, $parent_folder = null) { - /** - * @var $folder Folder - */ + public function updateSharedFolderOrFolder(?string $userId, int $folderId, ?string $title = null, ?int $parent_folder = null) { $folder = $this->folderMapper->find($folderId); if ($userId !== null || $userId !== $folder->getUserId()) { @@ -247,7 +241,7 @@ public function updateSharedFolderOrFolder($userId, $folderId, $title = null, $p if (isset($title)) { $folder->setTitle($title); $this->folderMapper->update($folder); - $this->eventDispatcher->dispatch(UpdateEvent::class, new UpdateEvent(TreeMapper::TYPE_FOLDER, $folder->getId())); + $this->eventDispatcher->dispatchTyped(new UpdateEvent(TreeMapper::TYPE_FOLDER, $folder->getId())); } if (isset($parent_folder)) { $parentFolder = $this->folderMapper->find($parent_folder); @@ -272,7 +266,6 @@ public function updateSharedFolderOrFolder($userId, $folderId, $title = null, $p public function createFolderPublicToken($folderId): string { $this->folderMapper->find($folderId); try { - /** @var PublicFolder $publicFolder */ $publicFolder = $this->publicFolderMapper->findByFolder($folderId); } catch (DoesNotExistException $e) { $publicFolder = new PublicFolder(); @@ -286,6 +279,7 @@ public function createFolderPublicToken($folderId): string { * @param $folderId * @throws DoesNotExistException * @throws MultipleObjectsReturnedException + * @throws Exception */ public function deleteFolderPublicToken($folderId): void { $publicFolder = $this->publicFolderMapper->findByFolder($folderId); @@ -305,9 +299,6 @@ public function deleteFolderPublicToken($folderId): void { * @throws Exception */ public function createShare($folderId, $participant, int $type, bool $canWrite = false, bool $canShare = false): Share { - /** - * @var $folder Folder - */ $folder = $this->folderMapper->find($folderId); $share = new Share(); diff --git a/tests/BackgroundJobTest.php b/tests/BackgroundJobTest.php index 5916ea36cd..979a64c0c3 100644 --- a/tests/BackgroundJobTest.php +++ b/tests/BackgroundJobTest.php @@ -181,7 +181,7 @@ public function testGCJob() : void { */ public function singleBookmarksProvider() { return array_map(function ($props) { - return Bookmark::fromArray($props); + return Bookmark::fromArray([...$props, 'userId' => 'test']); }, [ 'Simple URL with title and description' => ['url' => 'https://google.com/', 'title' => 'Google', 'description' => 'Search engine'], 'Simple URL with title' => ['url' => 'https://nextcloud.com/', 'title' => 'Nextcloud'], diff --git a/tests/BookmarkControllerTest.php b/tests/BookmarkControllerTest.php index dafcbbcca2..59f277b4e5 100644 --- a/tests/BookmarkControllerTest.php +++ b/tests/BookmarkControllerTest.php @@ -523,7 +523,7 @@ public function testPublicReadNotFound(): void { $output = $this->publicController->getSingleBookmark(987); $data = $output->getData(); $this->assertSame('error', $data['status'], var_export($data, true)); - $this->assertSame(403, $output->getStatus()); + $this->assertSame(404, $output->getStatus()); } /** @@ -541,7 +541,7 @@ public function testPublicget(): void { $output = $this->publicController->getBookmarks(-1, null, 'or', '', [], 10, false, $this->folder2->getId()); $data = $output->getData(); $this->assertEquals('success', $data['status'], var_export($data, true)); - $this->assertCount(1, $data['data']); // TODO: 1-level search Limit! + $this->assertCount(1, $data['data']); } /** @@ -591,7 +591,8 @@ public function testPublicDeleteBookmarkFail(): void { $this->authorizer->setUserId(null); $res = $this->publicController->deleteBookmark($this->bookmark1Id); - $this->assertEquals('error', $res->getData()['status'], var_export($res->getData(), true)); + $this->assertEquals('success', $res->getData()['status'], var_export($res->getData(), true)); + $this->bookmarkMapper->find($this->bookmark1Id); } /** diff --git a/tests/FolderControllerTest.php b/tests/FolderControllerTest.php index 9ba2ad2c6e..18549e206f 100644 --- a/tests/FolderControllerTest.php +++ b/tests/FolderControllerTest.php @@ -708,7 +708,7 @@ public function testDeletePublicFail(): void { $this->authorizer->setUserId(null); $output = $this->public->deleteFolder($this->folder1->getId()); $data = $output->getData(); - $this->assertEquals('error', $data['status'], var_export($data, true)); + $this->assertEquals('success', $data['status'], var_export($data, true)); $output = $this->public->getFolder($this->folder1->getId()); $data = $output->getData(); $this->assertEquals('success', $data['status'], var_export($data, true)); @@ -1118,7 +1118,8 @@ public function testDeleteShareSharee($participant, $type, $canWrite, $canShare) if ($canShare) { $this->assertEquals('success', $data['status'], var_export($data, true)); } else { - $this->assertEquals('error', $data['status'], var_export($data, true)); + $this->assertEquals('success', $data['status'], var_export($data, true)); + $this->shareMapper->find($shareId); } }