all: force permissions for external shares

pull/465/head
Varun Patil 2023-03-09 16:39:26 -08:00
parent b3fa60ce0c
commit af2a095059
7 changed files with 98 additions and 12 deletions

View File

@ -27,6 +27,7 @@ use OCA\Memories\AppInfo\Application;
use OCA\Memories\Db\TimelineQuery; use OCA\Memories\Db\TimelineQuery;
use OCA\Memories\Db\TimelineRoot; use OCA\Memories\Db\TimelineRoot;
use OCA\Memories\Exif; use OCA\Memories\Exif;
use OCA\Memories\Util;
use OCP\App\IAppManager; use OCP\App\IAppManager;
use OCP\AppFramework\Controller; use OCP\AppFramework\Controller;
use OCP\AppFramework\Http; use OCP\AppFramework\Http;
@ -168,6 +169,9 @@ class ApiBase extends Controller
} }
$userFolder = $this->rootFolder->getUserFolder($user->getUID()); $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); return $this->getOneFileFromFolder($userFolder, $id);
} }
@ -189,7 +193,10 @@ class ApiBase extends Controller
$folder = $this->rootFolder->getUserFolder($owner); $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); $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 // Folder share
if ($share = $this->getShareNode()) { 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) { } catch (\Exception $e) {
} }
@ -295,6 +305,9 @@ class ApiBase extends Controller
throw new \Exception('Share not found or invalid'); throw new \Exception('Share not found or invalid');
} }
// Force permissions from the share onto the node
Util::forcePermissions($node, $share->getPermissions());
return $node; return $node;
} }
@ -385,25 +398,35 @@ class ApiBase extends Controller
/** /**
* Helper to get one file or null from a fiolder. * 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 // Check for permissions and get numeric Id
$file = $folder->getById($id); $file = $folder->getById($id);
if (0 === \count($file)) { if (0 === \count($file)) {
return null; return null;
} }
$file = $file[0];
// Check if node is a file // Check if node is a file
if (!$file[0] instanceof File) { if (!$file instanceof File) {
return null; return null;
} }
// Check read permission // Check read permission
if (!($file[0]->getPermissions() & \OCP\Constants::PERMISSION_READ)) { if (!$file->isReadable()) {
return null; return null;
} }
return $file[0]; // Force file permissions if required
if ($perm >= 0) {
Util::forcePermissions($file, $perm);
}
return $file;
} }
} }

View File

@ -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); return new JSONResponse($info, Http::STATUS_OK);
} }

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace OCA\Memories; namespace OCA\Memories;
use OCP\App\IAppManager; use OCP\App\IAppManager;
use OCP\Files\Node;
use OCP\IConfig; use OCP\IConfig;
class Util class Util
@ -127,6 +128,57 @@ class Util
return true; 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 * Check if any encryption is enabled that we can not cope with
* such as end-to-end encryption. * such as end-to-end encryption.

View File

@ -172,7 +172,7 @@ export default defineComponent({
}, },
canEdit(): boolean { canEdit(): boolean {
return !this.$route.name?.endsWith("-share"); return this.baseInfo?.permissions?.includes("U");
}, },
/** Date taken info */ /** Date taken info */
@ -246,7 +246,11 @@ export default defineComponent({
/** Image info */ /** Image info */
imageInfo(): string | null { imageInfo(): string | null {
return this.fileInfo?.basename || (<any>this.fileInfo)?.name; return (
this.fileInfo?.originalBasename ||
this.fileInfo?.basename ||
(<any>this.fileInfo)?.name
);
}, },
imageInfoSub(): string[] | null { imageInfoSub(): string[] | null {

View File

@ -1,7 +1,7 @@
<template> <template>
<aside class="app-sidebar" v-if="reducedOpen"> <aside class="app-sidebar" v-if="reducedOpen">
<div class="title"> <div class="title">
<h2>{{ filename }}</h2> <h2>{{ basename }}</h2>
<NcActions :inline="1"> <NcActions :inline="1">
<NcActionButton :aria-label="t('memories', 'Close')" @click="close()"> <NcActionButton :aria-label="t('memories', 'Close')" @click="close()">
@ -40,7 +40,7 @@ export default defineComponent({
return { return {
nativeOpen: false, nativeOpen: false,
reducedOpen: false, reducedOpen: false,
filename: "", basename: "",
}; };
}, },
@ -72,7 +72,7 @@ export default defineComponent({
} else { } else {
this.reducedOpen = true; this.reducedOpen = true;
await this.$nextTick(); await this.$nextTick();
this.filename = file.basename; this.basename = file.originalBasename || file.basename;
(<any>this.$refs.metadata)?.update(file); (<any>this.$refs.metadata)?.update(file);
emit("memories:sidebar:opened", null); emit("memories:sidebar:opened", null);

View File

@ -285,6 +285,7 @@ export function getAlbumFileInfos(
filename: `${collection}/${basename}`, filename: `${collection}/${basename}`,
originalFilename: `${collection}/${basename}`, originalFilename: `${collection}/${basename}`,
basename: basename, basename: basename,
originalBasename: photo.basename,
mime: photo.mimetype, mime: photo.mimetype,
hasPreview: true, hasPreview: true,
etag: photo.etag, etag: photo.etag,

View File

@ -9,6 +9,8 @@ export type IFileInfo = {
originalFilename?: string; originalFilename?: string;
/** Base name of file e.g. Qx0dq7dvEXA.jpg */ /** Base name of file e.g. Qx0dq7dvEXA.jpg */
basename: string; basename: string;
/** Original base name, e.g. in albums without the file id */
originalBasename?: string;
/** Etag identifier */ /** Etag identifier */
etag: string; etag: string;
/** File has preview available */ /** File has preview available */
@ -78,6 +80,7 @@ export type IPhoto = {
datetaken: number; datetaken: number;
address?: string; address?: string;
tags: { [id: string]: string }; tags: { [id: string]: string };
permissions: string;
exif?: { exif?: {
Rotation?: number; Rotation?: number;
Orientation?: number; Orientation?: number;