diff --git a/lib/Controller/ApiBase.php b/lib/Controller/ApiBase.php index 9fb10268..d4fe00fb 100644 --- a/lib/Controller/ApiBase.php +++ b/lib/Controller/ApiBase.php @@ -27,6 +27,7 @@ use OCA\Memories\AppInfo\Application; use OCA\Memories\Db\TimelineQuery; use OCA\Memories\Db\TimelineRoot; use OCA\Memories\Exif; +use OCA\Memories\Util; use OCP\App\IAppManager; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; @@ -168,6 +169,9 @@ class ApiBase extends Controller } $userFolder = $this->rootFolder->getUserFolder($user->getUID()); + // No need to force permissions when reading + // from the user's own folder. This includes shared + // folders and files from other users. return $this->getOneFileFromFolder($userFolder, $id); } @@ -189,7 +193,10 @@ class ApiBase extends Controller $folder = $this->rootFolder->getUserFolder($owner); - return $this->getOneFileFromFolder($folder, $id); + // Album files are always read-only + // Note that albums have lowest priority, so it means the + // user doesn't have access to the file in their own folder. + return $this->getOneFileFromFolder($folder, $id, \OCP\Constants::PERMISSION_READ); } /** @@ -214,12 +221,15 @@ class ApiBase extends Controller $folder = $this->rootFolder->getUserFolder($owner); - return $this->getOneFileFromFolder($folder, $id); + // Public albums are always read-only + return $this->getOneFileFromFolder($folder, $id, \OCP\Constants::PERMISSION_READ); } // Folder share if ($share = $this->getShareNode()) { - return $this->getOneFileFromFolder($share, $id); + // Public shares may allow editing + // Just use the same permissions as the share + return $this->getOneFileFromFolder($share, $id, $share->getPermissions()); } } catch (\Exception $e) { } @@ -295,6 +305,9 @@ class ApiBase extends Controller throw new \Exception('Share not found or invalid'); } + // Force permissions from the share onto the node + Util::forcePermissions($node, $share->getPermissions()); + return $node; } @@ -385,25 +398,35 @@ class ApiBase extends Controller /** * Helper to get one file or null from a fiolder. + * + * @param Folder $folder Folder to search in + * @param int $id Id of the file + * @param int $perm Permissions to force on the file */ - private function getOneFileFromFolder(Folder $folder, int $id): ?File + private function getOneFileFromFolder(Folder $folder, int $id, int $perm = -1): ?File { // Check for permissions and get numeric Id $file = $folder->getById($id); if (0 === \count($file)) { return null; } + $file = $file[0]; // Check if node is a file - if (!$file[0] instanceof File) { + if (!$file instanceof File) { return null; } // Check read permission - if (!($file[0]->getPermissions() & \OCP\Constants::PERMISSION_READ)) { + if (!$file->isReadable()) { return null; } - return $file[0]; + // Force file permissions if required + if ($perm >= 0) { + Util::forcePermissions($file, $perm); + } + + return $file; } } diff --git a/lib/Controller/ImageController.php b/lib/Controller/ImageController.php index 3a3c00dc..17c9f25a 100644 --- a/lib/Controller/ImageController.php +++ b/lib/Controller/ImageController.php @@ -206,6 +206,9 @@ class ImageController extends ApiBase } } + // Inject permissions and convert to string + $info['permissions'] = \OCA\Memories\Util::permissionsToStr($file->getPermissions()); + return new JSONResponse($info, Http::STATUS_OK); } diff --git a/lib/Util.php b/lib/Util.php index 7814a4b4..6023919d 100644 --- a/lib/Util.php +++ b/lib/Util.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace OCA\Memories; use OCP\App\IAppManager; +use OCP\Files\Node; use OCP\IConfig; class Util @@ -127,6 +128,57 @@ class Util return true; } + /** + * Force a fileinfo value on a node. + * This is a hack to avoid subclassing everything. + * + * @param mixed $node File to patch + * @param mixed $key Key to set + * @param mixed $value Value to set + */ + public static function forceFileInfo(Node &$node, $key, $value) + { + /** @var \OC\Files\Node\Node */ + $node = $node; + $node->getFileInfo()[$key] = $value; + } + + /** + * Force permissions on a node. + * + * @param mixed $node File to patch + * @param mixed $permissions Permissions to set + */ + public static function forcePermissions(Node &$node, int $permissions) + { + self::forceFileInfo($node, 'permissions', $permissions); + } + + /** + * Convert permissions to string. + */ + public static function permissionsToStr(int $permissions): string + { + $str = ''; + if ($permissions & \OCP\Constants::PERMISSION_CREATE) { + $str .= 'C'; + } + if ($permissions & \OCP\Constants::PERMISSION_READ) { + $str .= 'R'; + } + if ($permissions & \OCP\Constants::PERMISSION_UPDATE) { + $str .= 'U'; + } + if ($permissions & \OCP\Constants::PERMISSION_DELETE) { + $str .= 'D'; + } + if ($permissions & \OCP\Constants::PERMISSION_SHARE) { + $str .= 'S'; + } + + return $str; + } + /** * Check if any encryption is enabled that we can not cope with * such as end-to-end encryption. diff --git a/src/components/Metadata.vue b/src/components/Metadata.vue index 55862779..eb6f048c 100644 --- a/src/components/Metadata.vue +++ b/src/components/Metadata.vue @@ -172,7 +172,7 @@ export default defineComponent({ }, canEdit(): boolean { - return !this.$route.name?.endsWith("-share"); + return this.baseInfo?.permissions?.includes("U"); }, /** Date taken info */ @@ -246,7 +246,11 @@ export default defineComponent({ /** Image info */ imageInfo(): string | null { - return this.fileInfo?.basename || (this.fileInfo)?.name; + return ( + this.fileInfo?.originalBasename || + this.fileInfo?.basename || + (this.fileInfo)?.name + ); }, imageInfoSub(): string[] | null { diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue index 6ecaf733..8e191b00 100644 --- a/src/components/Sidebar.vue +++ b/src/components/Sidebar.vue @@ -1,7 +1,7 @@