From 4d8b4967090e5e9fa62d2f1b389d14dc47bf00c8 Mon Sep 17 00:00:00 2001 From: Hendrik Leidinger Date: Fri, 17 Jul 2026 12:04:59 -0700 Subject: [PATCH] fix: fields endpoint was not checking for read/write permissions Signed-off-by: Hendrik Leidinger Assisted-by: Claude Code:Opus 4.8 --- lib/Controller/TemplateFieldController.php | 40 +++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/lib/Controller/TemplateFieldController.php b/lib/Controller/TemplateFieldController.php index 0f604b82b8..fb8bcf4b94 100644 --- a/lib/Controller/TemplateFieldController.php +++ b/lib/Controller/TemplateFieldController.php @@ -12,17 +12,38 @@ use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCSController; +use OCP\Constants; +use OCP\Files\File; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; use OCP\IRequest; +use OCP\IUserSession; class TemplateFieldController extends OCSController { public function __construct( string $appName, IRequest $request, private TemplateFieldService $templateFieldService, + private IRootFolder $rootFolder, + private IUserSession $userSession, ) { parent::__construct($appName, $request); } + /** Resolve strictly within the acting user's scope (blocks IDOR). */ + private function getUserFile(int $fileId): File { + $user = $this->userSession->getUser(); + if ($user === null) { + throw new NotFoundException(); + } + $node = $this->rootFolder->getUserFolder($user->getUID())->getFirstNodeById($fileId); + if (!$node instanceof File) { + throw new NotFoundException(); + } + return $node; + } + /** * @param int $fileId * @return DataResponse @@ -30,9 +51,10 @@ public function __construct( #[NoAdminRequired] public function extractFields(int $fileId): DataResponse { try { - $fields = $this->templateFieldService->extractFields($fileId); - + $fields = $this->templateFieldService->extractFields($this->getUserFile($fileId)); return new DataResponse($fields, Http::STATUS_OK); + } catch (NotFoundException) { + return new DataResponse(['File not found'], Http::STATUS_NOT_FOUND); } catch (\Exception $e) { return new DataResponse(['Unable to extract fields from given file'], Http::STATUS_INTERNAL_SERVER_ERROR); } @@ -46,14 +68,24 @@ public function extractFields(int $fileId): DataResponse { #[NoAdminRequired] public function fillFields(int $fileId, array $fields = [], ?string $destination = null, ?string $convert = null): DataResponse { try { - $content = $this->templateFieldService->fillFields($fileId, $fields, $destination, $convert); + $file = $this->getUserFile($fileId); + + // Filling a PDF overwrites the source in place, so require write access. + if ($file->getMimeType() === 'application/pdf' + && !($file->getPermissions() & Constants::PERMISSION_UPDATE)) { + throw new NotPermittedException(); + } + $content = $this->templateFieldService->fillFields($file, $fields, $destination, $convert); if ($destination === null) { echo $content; die(); } - return new DataResponse([], Http::STATUS_OK); + } catch (NotFoundException) { + return new DataResponse(['File not found'], Http::STATUS_NOT_FOUND); + } catch (NotPermittedException) { + return new DataResponse(['No permission to modify this file'], Http::STATUS_FORBIDDEN); } catch (\Exception $e) { return new DataResponse(['Unable to fill fields into the given file'], Http::STATUS_INTERNAL_SERVER_ERROR); }