diff options
Diffstat (limited to 'apps/files/lib/Controller')
-rw-r--r-- | apps/files/lib/Controller/ApiController.php | 27 | ||||
-rw-r--r-- | apps/files/lib/Controller/ConversionApiController.php | 109 | ||||
-rw-r--r-- | apps/files/lib/Controller/DirectEditingController.php | 1 | ||||
-rw-r--r-- | apps/files/lib/Controller/DirectEditingViewController.php | 1 | ||||
-rw-r--r-- | apps/files/lib/Controller/TemplateController.php | 18 | ||||
-rw-r--r-- | apps/files/lib/Controller/ViewController.php | 55 |
6 files changed, 190 insertions, 21 deletions
diff --git a/apps/files/lib/Controller/ApiController.php b/apps/files/lib/Controller/ApiController.php index 5ba325f1bf8..8bb024fb698 100644 --- a/apps/files/lib/Controller/ApiController.php +++ b/apps/files/lib/Controller/ApiController.php @@ -29,8 +29,11 @@ use OCP\AppFramework\Http\Response; use OCP\AppFramework\Http\StreamResponse; use OCP\Files\File; use OCP\Files\Folder; +use OCP\Files\InvalidPathException; use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\Files\Storage\ISharedStorage; use OCP\Files\StorageNotAvailableException; use OCP\IConfig; use OCP\IL10N; @@ -72,6 +75,7 @@ class ApiController extends Controller { * Gets a thumbnail of the specified file * * @since API version 1.0 + * @deprecated 32.0.0 Use the preview endpoint provided by core instead * * @param int $x Width of the thumbnail * @param int $y Height of the thumbnail @@ -85,26 +89,36 @@ class ApiController extends Controller { #[NoAdminRequired] #[NoCSRFRequired] #[StrictCookiesRequired] + #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)] public function getThumbnail($x, $y, $file) { if ($x < 1 || $y < 1) { return new DataResponse(['message' => 'Requested size must be numeric and a positive value.'], Http::STATUS_BAD_REQUEST); } try { - $file = $this->userFolder->get($file); - if ($file instanceof Folder) { + $file = $this->userFolder?->get($file); + if ($file === null + || !($file instanceof File) + || ($file->getId() <= 0) + ) { throw new NotFoundException(); } - if ($file->getId() <= 0) { - return new DataResponse(['message' => 'File not found.'], Http::STATUS_NOT_FOUND); + // Validate the user is allowed to download the file (preview is some kind of download) + /** @var ISharedStorage $storage */ + $storage = $file->getStorage(); + if ($storage->instanceOfStorage(ISharedStorage::class)) { + /** @var IShare $share */ + $share = $storage->getShare(); + if (!$share->canSeeContent()) { + throw new NotFoundException(); + } } - /** @var File $file */ $preview = $this->previewManager->getPreview($file, $x, $y, true); return new FileDisplayResponse($preview, Http::STATUS_OK, ['Content-Type' => $preview->getMimeType()]); - } catch (NotFoundException $e) { + } catch (NotFoundException|NotPermittedException|InvalidPathException) { return new DataResponse(['message' => 'File not found.'], Http::STATUS_NOT_FOUND); } catch (\Exception $e) { return new DataResponse([], Http::STATUS_BAD_REQUEST); @@ -277,6 +291,7 @@ class ApiController extends Controller { */ #[NoAdminRequired] #[ApiRoute(verb: 'GET', url: '/api/v1/folder-tree')] + #[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)] public function getFolderTree(string $path = '/', int $depth = 1): JSONResponse { $user = $this->userSession->getUser(); if (!($user instanceof IUser)) { diff --git a/apps/files/lib/Controller/ConversionApiController.php b/apps/files/lib/Controller/ConversionApiController.php new file mode 100644 index 00000000000..40a42d6ca4c --- /dev/null +++ b/apps/files/lib/Controller/ConversionApiController.php @@ -0,0 +1,109 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Files\Controller; + +use OC\Files\Utils\PathHelper; +use OC\ForbiddenException; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Attribute\ApiRoute; +use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\Attribute\UserRateLimit; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\OCS\OCSBadRequestException; +use OCP\AppFramework\OCS\OCSException; +use OCP\AppFramework\OCS\OCSForbiddenException; +use OCP\AppFramework\OCS\OCSNotFoundException; +use OCP\AppFramework\OCSController; +use OCP\Files\Conversion\IConversionManager; +use OCP\Files\File; +use OCP\Files\GenericFileException; +use OCP\Files\IRootFolder; +use OCP\IL10N; +use OCP\IRequest; +use function OCP\Log\logger; + +class ConversionApiController extends OCSController { + public function __construct( + string $appName, + IRequest $request, + private IConversionManager $fileConversionManager, + private IRootFolder $rootFolder, + private IL10N $l10n, + private ?string $userId, + ) { + parent::__construct($appName, $request); + } + + /** + * Converts a file from one MIME type to another + * + * @param int $fileId ID of the file to be converted + * @param string $targetMimeType The MIME type to which you want to convert the file + * @param string|null $destination The target path of the converted file. Written to a temporary file if left empty + * + * @return DataResponse<Http::STATUS_CREATED, array{path: string, fileId: int}, array{}> + * + * 201: File was converted and written to the destination or temporary file + * + * @throws OCSException The file was unable to be converted + * @throws OCSNotFoundException The file to be converted was not found + */ + #[NoAdminRequired] + #[UserRateLimit(limit: 25, period: 120)] + #[ApiRoute(verb: 'POST', url: '/api/v1/convert')] + public function convert(int $fileId, string $targetMimeType, ?string $destination = null): DataResponse { + $userFolder = $this->rootFolder->getUserFolder($this->userId); + $file = $userFolder->getFirstNodeById($fileId); + + // Also throw a 404 if the file is not readable to not leak information + if (!($file instanceof File) || $file->isReadable() === false) { + throw new OCSNotFoundException($this->l10n->t('The file cannot be found')); + } + + if ($destination !== null) { + $destination = PathHelper::normalizePath($destination); + $parentDir = dirname($destination); + + if (!$userFolder->nodeExists($parentDir)) { + throw new OCSNotFoundException($this->l10n->t('The destination path does not exist: %1$s', [$parentDir])); + } + + if (!$userFolder->get($parentDir)->isCreatable()) { + throw new OCSForbiddenException($this->l10n->t('You do not have permission to create a file at the specified location')); + } + + $destination = $userFolder->getFullPath($destination); + } + + try { + $convertedFile = $this->fileConversionManager->convert($file, $targetMimeType, $destination); + } catch (ForbiddenException $e) { + throw new OCSForbiddenException($e->getMessage()); + } catch (GenericFileException $e) { + throw new OCSBadRequestException($e->getMessage()); + } catch (\Exception $e) { + logger('files')->error($e->getMessage(), ['exception' => $e]); + throw new OCSException($this->l10n->t('The file could not be converted.')); + } + + $convertedFileRelativePath = $userFolder->getRelativePath($convertedFile); + if ($convertedFileRelativePath === null) { + throw new OCSNotFoundException($this->l10n->t('Could not get relative path to converted file')); + } + + $file = $userFolder->get($convertedFileRelativePath); + $fileId = $file->getId(); + + return new DataResponse([ + 'path' => $convertedFileRelativePath, + 'fileId' => $fileId, + ], Http::STATUS_CREATED); + } +} diff --git a/apps/files/lib/Controller/DirectEditingController.php b/apps/files/lib/Controller/DirectEditingController.php index 2c910006df5..c8addc33e98 100644 --- a/apps/files/lib/Controller/DirectEditingController.php +++ b/apps/files/lib/Controller/DirectEditingController.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/files/lib/Controller/DirectEditingViewController.php b/apps/files/lib/Controller/DirectEditingViewController.php index 1d78e2af0e0..b13e68f7766 100644 --- a/apps/files/lib/Controller/DirectEditingViewController.php +++ b/apps/files/lib/Controller/DirectEditingViewController.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/files/lib/Controller/TemplateController.php b/apps/files/lib/Controller/TemplateController.php index 3321fb5f119..ee4c86941c7 100644 --- a/apps/files/lib/Controller/TemplateController.php +++ b/apps/files/lib/Controller/TemplateController.php @@ -53,6 +53,24 @@ class TemplateController extends OCSController { } /** + * List the fields for the template specified by the given file ID + * + * @param int $fileId File ID of the template + * @return DataResponse<Http::STATUS_OK, array<string, FilesTemplateField>, array{}> + * + * 200: Fields returned + */ + #[NoAdminRequired] + public function listTemplateFields(int $fileId): DataResponse { + $fields = $this->templateManager->listTemplateFields($fileId); + + return new DataResponse( + array_merge([], ...$fields), + Http::STATUS_OK + ); + } + + /** * Create a template * * @param string $filePath Path of the file diff --git a/apps/files/lib/Controller/ViewController.php b/apps/files/lib/Controller/ViewController.php index 4dd9f4141e2..ecf21cef313 100644 --- a/apps/files/lib/Controller/ViewController.php +++ b/apps/files/lib/Controller/ViewController.php @@ -26,6 +26,7 @@ use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\Http\Response; use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Services\IInitialState; +use OCP\Authentication\TwoFactorAuth\IRegistry; use OCP\Collaboration\Resources\LoadAdditionalScriptsEvent as ResourcesLoadAdditionalScriptsEvent; use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Folder; @@ -60,6 +61,7 @@ class ViewController extends Controller { private UserConfig $userConfig, private ViewConfig $viewConfig, private FilenameValidator $filenameValidator, + private IRegistry $twoFactorRegistry, ) { parent::__construct($appName, $request); } @@ -82,16 +84,18 @@ class ViewController extends Controller { */ #[NoAdminRequired] #[NoCSRFRequired] - public function showFile(?string $fileid = null): Response { + public function showFile(?string $fileid = null, ?string $opendetails = null, ?string $openfile = null): Response { if (!$fileid) { return new RedirectResponse($this->urlGenerator->linkToRoute('files.view.index')); } // This is the entry point from the `/f/{fileid}` URL which is hardcoded in the server. try { - return $this->redirectToFile((int)$fileid); + return $this->redirectToFile((int)$fileid, $opendetails, $openfile); } catch (NotFoundException $e) { - return new RedirectResponse($this->urlGenerator->linkToRoute('files.view.index', ['fileNotFound' => true])); + // Keep the fileid even if not found, it will be used + // to detect the file could not be found and warn the user + return new RedirectResponse($this->urlGenerator->linkToRoute('files.view.indexViewFileid', ['fileid' => $fileid, 'view' => 'files'])); } } @@ -100,38 +104,35 @@ class ViewController extends Controller { * @param string $dir * @param string $view * @param string $fileid - * @param bool $fileNotFound * @return TemplateResponse|RedirectResponse */ #[NoAdminRequired] #[NoCSRFRequired] - public function indexView($dir = '', $view = '', $fileid = null, $fileNotFound = false) { - return $this->index($dir, $view, $fileid, $fileNotFound); + public function indexView($dir = '', $view = '', $fileid = null) { + return $this->index($dir, $view, $fileid); } /** * @param string $dir * @param string $view * @param string $fileid - * @param bool $fileNotFound * @return TemplateResponse|RedirectResponse */ #[NoAdminRequired] #[NoCSRFRequired] - public function indexViewFileid($dir = '', $view = '', $fileid = null, $fileNotFound = false) { - return $this->index($dir, $view, $fileid, $fileNotFound); + public function indexViewFileid($dir = '', $view = '', $fileid = null) { + return $this->index($dir, $view, $fileid); } /** * @param string $dir * @param string $view * @param string $fileid - * @param bool $fileNotFound * @return TemplateResponse|RedirectResponse */ #[NoAdminRequired] #[NoCSRFRequired] - public function index($dir = '', $view = '', $fileid = null, $fileNotFound = false) { + public function index($dir = '', $view = '', $fileid = null) { if ($fileid !== null && $view !== 'trashbin') { try { return $this->redirectToFileIfInTrashbin((int)$fileid); @@ -143,7 +144,8 @@ class ViewController extends Controller { Util::addInitScript('files', 'init'); Util::addScript('files', 'main'); - $userId = $this->userSession->getUser()->getUID(); + $user = $this->userSession->getUser(); + $userId = $user->getUID(); // If the file doesn't exists in the folder and // exists in only one occurrence, redirect to that file @@ -159,8 +161,6 @@ class ViewController extends Controller { if (count($nodes) === 1 && $relativePath !== $dir && $nodePath !== $dir) { return $this->redirectToFile((int)$fileid); } - } else { // fileid does not exist anywhere - $fileNotFound = true; } } @@ -194,9 +194,19 @@ class ViewController extends Controller { $this->eventDispatcher->dispatchTyped(new LoadViewer()); } + $this->initialState->provideInitialState('templates_enabled', ($this->config->getSystemValueString('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton') !== '') || ($this->config->getSystemValueString('templatedirectory', \OC::$SERVERROOT . '/core/skeleton/Templates') !== '')); $this->initialState->provideInitialState('templates_path', $this->templateManager->hasTemplateDirectory() ? $this->templateManager->getTemplatePath() : false); $this->initialState->provideInitialState('templates', $this->templateManager->listCreators()); + $isTwoFactorEnabled = false; + foreach ($this->twoFactorRegistry->getProviderStates($user) as $providerId => $providerState) { + if ($providerId !== 'backup_codes' && $providerState === true) { + $isTwoFactorEnabled = true; + } + } + + $this->initialState->provideInitialState('isTwoFactorEnabled', $isTwoFactorEnabled); + $response = new TemplateResponse( Application::APP_ID, 'index', @@ -248,10 +258,12 @@ class ViewController extends Controller { * Redirects to the file list and highlight the given file id * * @param int $fileId file id to show + * @param string|null $openDetails open details parameter + * @param string|null $openFile open file parameter * @return RedirectResponse redirect response or not found response * @throws NotFoundException */ - private function redirectToFile(int $fileId) { + private function redirectToFile(int $fileId, ?string $openDetails = null, ?string $openFile = null): RedirectResponse { $uid = $this->userSession->getUser()->getUID(); $baseFolder = $this->rootFolder->getUserFolder($uid); $node = $baseFolder->getFirstNodeById($fileId); @@ -273,6 +285,19 @@ class ViewController extends Controller { // open the file by default (opening the viewer) $params['openfile'] = 'true'; } + + // Forward open parameters if any. + // - openfile is true by default + // - opendetails is undefined by default + // - both will be evaluated as truthy + if ($openDetails !== null) { + $params['opendetails'] = $openDetails !== 'false' ? 'true' : 'false'; + } + + if ($openFile !== null) { + $params['openfile'] = $openFile !== 'false' ? 'true' : 'false'; + } + return new RedirectResponse($this->urlGenerator->linkToRoute('files.view.indexViewFileid', $params)); } |