all: force permissions for external shares
parent
b3fa60ce0c
commit
af2a095059
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
52
lib/Util.php
52
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.
|
||||
|
|
|
@ -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 || (<any>this.fileInfo)?.name;
|
||||
return (
|
||||
this.fileInfo?.originalBasename ||
|
||||
this.fileInfo?.basename ||
|
||||
(<any>this.fileInfo)?.name
|
||||
);
|
||||
},
|
||||
|
||||
imageInfoSub(): string[] | null {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<aside class="app-sidebar" v-if="reducedOpen">
|
||||
<div class="title">
|
||||
<h2>{{ filename }}</h2>
|
||||
<h2>{{ basename }}</h2>
|
||||
|
||||
<NcActions :inline="1">
|
||||
<NcActionButton :aria-label="t('memories', 'Close')" @click="close()">
|
||||
|
@ -40,7 +40,7 @@ export default defineComponent({
|
|||
return {
|
||||
nativeOpen: false,
|
||||
reducedOpen: false,
|
||||
filename: "",
|
||||
basename: "",
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -72,7 +72,7 @@ export default defineComponent({
|
|||
} else {
|
||||
this.reducedOpen = true;
|
||||
await this.$nextTick();
|
||||
this.filename = file.basename;
|
||||
this.basename = file.originalBasename || file.basename;
|
||||
|
||||
(<any>this.$refs.metadata)?.update(file);
|
||||
emit("memories:sidebar:opened", null);
|
||||
|
|
|
@ -285,6 +285,7 @@ export function getAlbumFileInfos(
|
|||
filename: `${collection}/${basename}`,
|
||||
originalFilename: `${collection}/${basename}`,
|
||||
basename: basename,
|
||||
originalBasename: photo.basename,
|
||||
mime: photo.mimetype,
|
||||
hasPreview: true,
|
||||
etag: photo.etag,
|
||||
|
|
|
@ -9,6 +9,8 @@ export type IFileInfo = {
|
|||
originalFilename?: string;
|
||||
/** Base name of file e.g. Qx0dq7dvEXA.jpg */
|
||||
basename: string;
|
||||
/** Original base name, e.g. in albums without the file id */
|
||||
originalBasename?: string;
|
||||
/** Etag identifier */
|
||||
etag: string;
|
||||
/** File has preview available */
|
||||
|
@ -78,6 +80,7 @@ export type IPhoto = {
|
|||
datetaken: number;
|
||||
address?: string;
|
||||
tags: { [id: string]: string };
|
||||
permissions: string;
|
||||
exif?: {
|
||||
Rotation?: number;
|
||||
Orientation?: number;
|
||||
|
|
Loading…
Reference in New Issue