diff --git a/appinfo/routes.php b/appinfo/routes.php index 105e1dab69..ba06093291 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -38,7 +38,7 @@ ['name' => 'settings#uploadFontFile', 'url' => 'settings/fonts', 'verb' => 'POST'], [ 'name' => 'settings#getSettingsFile', - 'url' => 'settings/{type}/{category}/{name}', + 'url' => 'settings/{type}/{token}/{category}/{name}', 'verb' => 'GET', 'requirements' => [ 'type' => '[a-zA-Z0-9_\-]+', diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index f11bc8ac06..9b5507ea49 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -32,6 +32,7 @@ use OCP\Util; use Psr\Log\LoggerInterface; use Symfony\Component\Console\Output\NullOutput; +use OCA\Richdocuments\Db\WopiMapper; class SettingsController extends Controller { // TODO adapt overview generation if we add more font mimetypes @@ -58,6 +59,7 @@ public function __construct( private SettingsService $settingsService, private LoggerInterface $logger, private IURLGenerator $urlGenerator, + private WopiMapper $wopiMapper, private ?string $userId, ) { parent::__construct($appName, $request); @@ -484,8 +486,14 @@ public function uploadFontFile(): JSONResponse { * @PublicPage * @NoCSRFRequired **/ - public function getSettingsFile(string $type, string $category, string $name) { + public function getSettingsFile(string $type, string $token, string $category, string $name) { try { + $wopi = $this->wopiMapper->getWopiForToken($token); + if ($type === 'userconfig') + { + $userId = $wopi->getEditorUid() ?: $wopi->getOwnerUid(); + $type = $type . '/' . $userId; + } $systemFile = $this->settingsService->getSettingsFile($type, $category, $name); return new DataDisplayResponse( $systemFile->getContent(), diff --git a/lib/Controller/WopiController.php b/lib/Controller/WopiController.php index 95427d3893..cf6353f981 100644 --- a/lib/Controller/WopiController.php +++ b/lib/Controller/WopiController.php @@ -139,7 +139,9 @@ public function checkFileInfo(string $fileId, string $access_token): JSONRespons $userId = !$isPublic ? $wopi->getEditorUid() : $guestUserId; - $userSettings = $this->generateSettings($userId, 'userconfig'); + if (!$isPublic) { + $userSettings = $this->generateSettings($userId, 'userconfig'); + } $sharedSettings = $this->generateSettings($userId, 'systemconfig'); $response = [ @@ -174,10 +176,13 @@ public function checkFileInfo(string $fileId, string $access_token): JSONRespons 'EnableRemoteAIContent' => $isTaskProcessingEnabled, 'HasContentRange' => true, 'ServerPrivateInfo' => [], - 'UserSettings' => $userSettings, 'SharedSettings' => $sharedSettings, ]; + if (!$isPublic) { + $response['UserSettings'] = $userSettings; + } + $enableZotero = $this->config->getAppValue(Application::APPNAME, 'zoteroEnabled', 'yes') === 'yes'; if (!$isPublic && $enableZotero) { $zoteroAPIKey = $this->config->getUserValue($wopi->getEditorUid(), 'richdocuments', 'zoteroAPIKey', ''); @@ -406,13 +411,12 @@ public function getSettings(string $type, string $access_token): JSONResponse { if ($wopi->getTokenType() !== Wopi::TOKEN_TYPE_SETTING_AUTH) { return new JSONResponse(['error' => 'Invalid token type'], Http::STATUS_BAD_REQUEST); } + + $isPublic = empty($wopi->getEditorUid()); + $guestUserId = 'Guest-' . \OC::$server->getSecureRandom()->generate(8); + $userId = !$isPublic ? $wopi->getEditorUid() : $guestUserId; - $user = $this->userManager->get($wopi->getOwnerUid()); - if (!$user || !$this->groupManager->isAdmin($user->getUID())) { - return new JSONResponse(['error' => 'Access denied'], Http::STATUS_BAD_REQUEST); - } - - $userConfig = $this->settingsService->generateSettingsConfig($type); + $userConfig = $this->settingsService->generateSettingsConfig($type, $userId); return new JSONResponse($userConfig, Http::STATUS_OK); } catch (UnknownTokenException|ExpiredTokenException $e) { $this->logger->debug($e->getMessage(), ['exception' => $e]); @@ -431,6 +435,7 @@ public function uploadSettingsFile(string $fileId, string $access_token): JSONRe try { $wopi = $this->wopiMapper->getWopiForToken($access_token); + $userId = $wopi->getEditorUid(); // TODO: auth - for admin?? $content = fopen('php://input', 'rb'); if (!$content) { @@ -442,7 +447,7 @@ public function uploadSettingsFile(string $fileId, string $access_token): JSONRe // Use the fileId as a file path URL (e.g., "/settings/systemconfig/wordbook/en_US%20(1).dic") $settingsUrl = new SettingsUrl($fileId); - $result = $this->settingsService->uploadFile($settingsUrl, $fileContent); + $result = $this->settingsService->uploadFile($settingsUrl, $fileContent, $userId); return new JSONResponse([ 'status' => 'success', @@ -475,8 +480,9 @@ public function deleteSettingsFile(string $fileId, string $access_token): JSONRe $type = $settingsUrl->getType(); $category = $settingsUrl->getCategory(); $fileName = $settingsUrl->getFileName(); + $userId = $wopi->getEditorUid(); - $this->settingsService->deleteSettingsFile($type, $category, $fileName); + $this->settingsService->deleteSettingsFile($type, $category, $fileName, $userId); return new JSONResponse([ 'status' => 'success', diff --git a/lib/Db/WopiMapper.php b/lib/Db/WopiMapper.php index 5bd3cc5cbf..0ed77392a0 100644 --- a/lib/Db/WopiMapper.php +++ b/lib/Db/WopiMapper.php @@ -72,6 +72,7 @@ public function generateUserSettingsToken($fileId, $userId, $version, $serverHos $wopi = Wopi::fromParams([ 'fileid' => $fileId, 'ownerUid' => $userId, + 'editorUid' => $userId, 'version' => $version, 'canwrite' => true, 'serverHost' => $serverHost, diff --git a/lib/Service/SettingsService.php b/lib/Service/SettingsService.php index 6bbf074f24..c3c6a17937 100644 --- a/lib/Service/SettingsService.php +++ b/lib/Service/SettingsService.php @@ -58,7 +58,7 @@ public function __construct( * @return ISimpleFolder */ - public function ensureDirectory(SettingsUrl $settingsUrl): ISimpleFolder { + public function ensureDirectory(SettingsUrl $settingsUrl, string $userId): ISimpleFolder { $type = $settingsUrl->getType(); $category = $settingsUrl->getCategory(); @@ -68,6 +68,14 @@ public function ensureDirectory(SettingsUrl $settingsUrl): ISimpleFolder { $baseFolder = $this->appData->newFolder($type); } + if ($type === 'userconfig') { + try { + $baseFolder = $baseFolder->getFolder($userId); + } catch (NotFoundException $e) { + $baseFolder = $baseFolder->newFolder($userId); + } + } + try { $categoryFolder = $baseFolder->getFolder($category); } catch (NotFoundException $e) { @@ -86,11 +94,11 @@ public function ensureDirectory(SettingsUrl $settingsUrl): ISimpleFolder { * @return array ['stamp' => string, 'uri' => string] */ - public function uploadFile(SettingsUrl $settingsUrl, string $fileData): array { - $categoryFolder = $this->ensureDirectory($settingsUrl); + public function uploadFile(SettingsUrl $settingsUrl, string $fileData, string $userId): array { + $categoryFolder = $this->ensureDirectory($settingsUrl, $userId); $fileName = $settingsUrl->getFileName(); $newFile = $categoryFolder->newFile($fileName, $fileData); - $fileUri = $this->generateFileUri($settingsUrl->getType(), $settingsUrl->getCategory(), $fileName); + $fileUri = $this->generateFileUri($settingsUrl->getType(), $settingsUrl->getCategory(), $fileName, $userId); return [ 'stamp' => $newFile->getETag(), @@ -105,7 +113,7 @@ public function uploadFile(SettingsUrl $settingsUrl, string $fileData): array { * @param string $category * @return array Each item has 'stamp' and 'uri'. */ - public function getCategoryFileList(string $type, string $category): array { + public function getCategoryFileList(string $type, string $category, string $userId): array { try { $categoryFolder = $this->appData->getFolder($type . '/' . $category); } catch (NotFoundException $e) { @@ -114,10 +122,10 @@ public function getCategoryFileList(string $type, string $category): array { $files = $categoryFolder->getDirectoryListing(); - return array_map(function (ISimpleFile $file) use ($type, $category) { + return array_map(function (ISimpleFile $file) use ($type, $category, $userId) { return [ 'stamp' => $file->getETag(), - 'uri' => $this->generateFileUri($type, $category, $file->getName()), + 'uri' => $this->generateFileUri($type, $category, $file->getName(), $userId), ]; }, $files); } @@ -155,17 +163,21 @@ public function generateIframeToken(string $type, string $userId): array { * @param string $type * @return array */ - public function generateSettingsConfig(string $type): array { + public function generateSettingsConfig(string $type, string $userId): array { $kind = $type === 'userconfig' ? 'user' : 'shared'; $config = [ 'kind' => $kind, ]; + if ($type === "userconfig") { + $type = $type . '/' . $userId; + } + $categories = $this->getAllCategories($type); foreach ($categories as $category) { - $files = $this->getCategoryFileList($type, $category); + $files = $this->getCategoryFileList($type, $category, $userId); $config[$category] = $files; } @@ -220,11 +232,20 @@ private function getCategoryDirFolderList(string $type) : array { * @param string $fileName * @return string */ - private function generateFileUri(string $type, string $category, string $fileName): string { + private function generateFileUri(string $type, string $category, string $fileName, string $userId): string { + + // Passing userId is dangerous so we have to trim from url... + if (strpos($type, '/') !== false) { + $type = explode('/', $type)[0]; + } + + $token = $this->generateIframeToken($type, $userId); + return $this->urlGenerator->linkToRouteAbsolute( 'richdocuments.settings.getSettingsFile', [ 'type' => $type, + 'token' => $token['token'], 'category' => $category, 'name' => $fileName, ] @@ -266,12 +287,21 @@ public function getSettingsFile(string $type, string $category, string $name): I * @param string $category * @param string $name */ - public function deleteSettingsFile(string $type, string $category, string $name): void { + public function deleteSettingsFile(string $type, string $category, string $name, string $userId): void { try { $baseFolder = $this->appData->getFolder($type); } catch (NotFoundException $e) { throw new NotFoundException("Type folder '{$type}' not found."); } + + if ($type === 'userconfig') { + try { + $baseFolder = $baseFolder->getFolder($userId); + } catch (NotFoundException $e) + { + throw new NotFoundException("User folder '{$userId}' not found."); + } + } try { $categoryFolder = $baseFolder->getFolder($category);