aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/TaskProcessing
diff options
context:
space:
mode:
authorMarcel Klehr <mklehr@gmx.net>2024-07-09 11:43:11 +0200
committerMarcel Klehr <mklehr@gmx.net>2024-07-17 13:55:55 +0200
commit4ac7f8275b5e89023c8c6c4f468d82d5c782c0d4 (patch)
tree2d88320bc29f0b21c29547505e8a1a7b0e3219c6 /lib/private/TaskProcessing
parentb06ce832d8f280b9c008b91c41757e8eab37dc77 (diff)
downloadnextcloud-server-4ac7f8275b5e89023c8c6c4f468d82d5c782c0d4.tar.gz
nextcloud-server-4ac7f8275b5e89023c8c6c4f468d82d5c782c0d4.zip
feat(TaskProcessing): Allow setting task results for file slots
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Diffstat (limited to 'lib/private/TaskProcessing')
-rw-r--r--lib/private/TaskProcessing/Manager.php129
-rw-r--r--lib/private/TaskProcessing/SynchronousBackgroundJob.php3
2 files changed, 89 insertions, 43 deletions
diff --git a/lib/private/TaskProcessing/Manager.php b/lib/private/TaskProcessing/Manager.php
index f0d1d4ba51a..234534936d4 100644
--- a/lib/private/TaskProcessing/Manager.php
+++ b/lib/private/TaskProcessing/Manager.php
@@ -18,10 +18,12 @@ use OCP\BackgroundJob\IJobList;
use OCP\DB\Exception;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\AppData\IAppDataFactory;
+use OCP\Files\Config\IUserMountCache;
use OCP\Files\File;
use OCP\Files\GenericFileException;
use OCP\Files\IAppData;
use OCP\Files\IRootFolder;
+use OCP\Files\Node;
use OCP\Files\NotPermittedException;
use OCP\Files\SimpleFS\ISimpleFile;
use OCP\IL10N;
@@ -77,7 +79,7 @@ class Manager implements IManager {
private \OCP\TextProcessing\IManager $textProcessingManager,
private \OCP\TextToImage\IManager $textToImageManager,
private \OCP\SpeechToText\ISpeechToTextManager $speechToTextManager,
- private \OCP\Share\IManager $shareManager,
+ private IUserMountCache $userMountCache,
) {
$this->appData = $appDataFactory->get('core');
}
@@ -561,19 +563,8 @@ class Manager implements IManager {
}
}
foreach ($ids as $fileId) {
- $node = $this->rootFolder->getFirstNodeById($fileId);
- if ($node === null) {
- $node = $this->rootFolder->getFirstNodeByIdInPath($fileId, '/' . $this->rootFolder->getAppDataDirectoryName() . '/');
- if ($node === null) {
- throw new ValidationException('Could not find file ' . $fileId);
- }
- }
- /** @var array{users:array<string,array{node_id:int, node_path: string}>, remote: array<string,array{node_id:int, node_path: string}>, mail: array<string,array{node_id:int, node_path: string}>} $accessList */
- $accessList = $this->shareManager->getAccessList($node, true, true);
- $userIds = array_map(fn ($id) => strval($id), array_keys($accessList['users']));
- if (!in_array($task->getUserId(), $userIds)) {
- throw new UnauthorizedException('User ' . $task->getUserId() . ' does not have access to file ' . $fileId);
- }
+ $this->validateFileId($fileId);
+ $this->validateUserAccessToFile($fileId, $task->getUserId());
}
// remove superfluous keys and set input
$task->setInput($this->removeSuperfluousArrayKeys($task->getInput(), $inputShape, $optionalInputShape));
@@ -643,7 +634,7 @@ class Manager implements IManager {
return true;
}
- public function setTaskResult(int $id, ?string $error, ?array $result): void {
+ public function setTaskResult(int $id, ?string $error, ?array $result, bool $isUsingFileIds = false): void {
// TODO: Not sure if we should rather catch the exceptions of getTask here and fail silently
$task = $this->getTask($id);
if ($task->getStatus() === Task::STATUS_CANCELLED) {
@@ -664,7 +655,11 @@ class Manager implements IManager {
$this->validateOutput($optionalOutputShape, $result, true);
$output = $this->removeSuperfluousArrayKeys($result, $outputShape, $optionalOutputShape);
// extract raw data and put it in files, replace it with file ids
- $output = $this->encapsulateOutputFileData($output, $outputShape, $optionalOutputShape);
+ if (!$isUsingFileIds) {
+ $output = $this->encapsulateOutputFileData($output, $outputShape, $optionalOutputShape);
+ } else {
+ $output = $this->validateOutputFileIds($output, $outputShape, $optionalOutputShape);
+ }
$task->setOutput($output);
$task->setProgress(1);
$task->setStatus(Task::STATUS_SUCCESSFUL);
@@ -711,16 +706,13 @@ class Manager implements IManager {
}
/**
- * Takes task input or output data and replaces fileIds with base64 data
+ * Takes task input data and replaces fileIds with File objects
*
* @param string|null $userId
* @param array<array-key, list<numeric|string>|numeric|string> $input
* @param ShapeDescriptor[] ...$specs the specs
* @return array<array-key, list<File|numeric|string>|numeric|string|File>
- * @throws GenericFileException
- * @throws LockedException
- * @throws NotPermittedException
- * @throws ValidationException
+ * @throws GenericFileException|LockedException|NotPermittedException|ValidationException|UnauthorizedException
*/
public function fillInputFileData(?string $userId, array $input, ...$specs): array {
if ($userId !== null) {
@@ -738,30 +730,14 @@ class Manager implements IManager {
continue;
}
if ($type->value < 10) {
- $node = $this->rootFolder->getFirstNodeById((int)$input[$key]);
- if ($node === null) {
- $node = $this->rootFolder->getFirstNodeByIdInPath((int)$input[$key], '/' . $this->rootFolder->getAppDataDirectoryName() . '/');
- if (!$node instanceof File) {
- throw new ValidationException('File id given for key "' . $key . '" is not a file');
- }
- } elseif (!$node instanceof File) {
- throw new ValidationException('File id given for key "' . $key . '" is not a file');
- }
- // TODO: Validate if userId has access to this file
+ $node = $this->validateFileId((int)$input[$key]);
+ $this->validateUserAccessToFile($input[$key], $userId);
$newInputOutput[$key] = $node;
} else {
$newInputOutput[$key] = [];
foreach ($input[$key] as $item) {
- $node = $this->rootFolder->getFirstNodeById((int)$item);
- if ($node === null) {
- $node = $this->rootFolder->getFirstNodeByIdInPath((int)$item, '/' . $this->rootFolder->getAppDataDirectoryName() . '/');
- if (!$node instanceof File) {
- throw new ValidationException('File id given for key "' . $key . '" is not a file');
- }
- } elseif (!$node instanceof File) {
- throw new ValidationException('File id given for key "' . $key . '" is not a file');
- }
- // TODO: Validate if userId has access to this file
+ $node = $this->validateFileId((int)$item);
+ $this->validateUserAccessToFile($item, $userId);
$newInputOutput[$key][] = $node;
}
}
@@ -851,7 +827,7 @@ class Manager implements IManager {
* @throws GenericFileException
* @throws LockedException
* @throws NotPermittedException
- * @throws ValidationException
+ * @throws ValidationException|UnauthorizedException
*/
public function prepareInputData(Task $task): array {
$taskTypes = $this->getAvailableTaskTypes();
@@ -884,4 +860,73 @@ class Manager implements IManager {
$taskEntity = \OC\TaskProcessing\Db\Task::fromPublicTask($task);
$this->taskMapper->update($taskEntity);
}
+
+ /**
+ * @param array $output
+ * @param ShapeDescriptor[] ...$specs the specs that define which keys to keep
+ * @return array
+ * @throws NotPermittedException
+ */
+ private function validateOutputFileIds(array $output, ...$specs): array {
+ $newOutput = [];
+ $spec = array_reduce($specs, fn ($carry, $spec) => $carry + $spec, []);
+ foreach($spec as $key => $descriptor) {
+ $type = $descriptor->getShapeType();
+ if (!isset($output[$key])) {
+ continue;
+ }
+ if (!in_array(EShapeType::getScalarType($type), [EShapeType::Image, EShapeType::Audio, EShapeType::Video, EShapeType::File], true)) {
+ $newOutput[$key] = $output[$key];
+ continue;
+ }
+ if ($type->value < 10) {
+ // Is scalar file ID
+ $newOutput[$key] = $this->validateFileId($output[$key]);
+ } else {
+ // Is list of file IDs
+ $newOutput = [];
+ foreach ($output[$key] as $item) {
+ $newOutput[$key][] = $this->validateFileId($item);
+ }
+ }
+ }
+ return $newOutput;
+ }
+
+ /**
+ * @param mixed $id
+ * @return Node
+ * @throws ValidationException
+ */
+ private function validateFileId(mixed $id): Node {
+ $node = $this->rootFolder->getFirstNodeById($id);
+ if ($node === null) {
+ $node = $this->rootFolder->getFirstNodeByIdInPath($id, '/' . $this->rootFolder->getAppDataDirectoryName() . '/');
+ if ($node === null) {
+ throw new ValidationException('Could not find file ' . $id);
+ } elseif (!$node instanceof File) {
+ throw new ValidationException('File with id "' . $id . '" is not a file');
+ }
+ } elseif (!$node instanceof File) {
+ throw new ValidationException('File with id "' . $id . '" is not a file');
+ }
+ return $node;
+ }
+
+ /**
+ * @param mixed $fileId
+ * @param string $userId
+ * @return void
+ * @throws UnauthorizedException
+ */
+ private function validateUserAccessToFile(mixed $fileId, ?string $userId): void {
+ if ($userId === null) {
+ throw new UnauthorizedException('User does not have access to file ' . $fileId);
+ }
+ $mounts = $this->userMountCache->getMountsForFileId($fileId);
+ $userIds = array_map(fn ($mount) => $mount->getUser()->getUID(), $mounts);
+ if (!in_array($userId, $userIds)) {
+ throw new UnauthorizedException('User ' . $userId . ' does not have access to file ' . $fileId);
+ }
+ }
}
diff --git a/lib/private/TaskProcessing/SynchronousBackgroundJob.php b/lib/private/TaskProcessing/SynchronousBackgroundJob.php
index 7f1ab623190..093882d4c1e 100644
--- a/lib/private/TaskProcessing/SynchronousBackgroundJob.php
+++ b/lib/private/TaskProcessing/SynchronousBackgroundJob.php
@@ -15,6 +15,7 @@ use OCP\Lock\LockedException;
use OCP\TaskProcessing\Exception\Exception;
use OCP\TaskProcessing\Exception\NotFoundException;
use OCP\TaskProcessing\Exception\ProcessingException;
+use OCP\TaskProcessing\Exception\UnauthorizedException;
use OCP\TaskProcessing\Exception\ValidationException;
use OCP\TaskProcessing\IManager;
use OCP\TaskProcessing\ISynchronousProvider;
@@ -54,7 +55,7 @@ class SynchronousBackgroundJob extends QueuedJob {
try {
try {
$input = $this->taskProcessingManager->prepareInputData($task);
- } catch (GenericFileException|NotPermittedException|LockedException|ValidationException $e) {
+ } catch (GenericFileException|NotPermittedException|LockedException|ValidationException|UnauthorizedException $e) {
$this->logger->warning('Failed to prepare input data for a TaskProcessing task with synchronous provider ' . $provider->getId(), ['exception' => $e]);
$this->taskProcessingManager->setTaskResult($task->getId(), $e->getMessage(), null);
// Schedule again