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\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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
52
lib/Util.php
52
lib/Util.php
|
@ -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.
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue