311 lines
9.4 KiB
PHP
311 lines
9.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
/**
|
|
* SPDX-FileCopyrightText: 2019 F7cloud GmbH and F7cloud contributors
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
namespace OCA\GroupFolders\ACL;
|
|
|
|
use Icewind\Streams\IteratorDirectory;
|
|
use OC\Files\Storage\Wrapper\Wrapper;
|
|
use OCP\Constants;
|
|
use OCP\Files\Cache\ICache;
|
|
use OCP\Files\Cache\IScanner;
|
|
use OCP\Files\Storage\IConstructableStorage;
|
|
use OCP\Files\Storage\IStorage;
|
|
|
|
class ACLStorageWrapper extends Wrapper implements IConstructableStorage {
|
|
private readonly ACLManager $aclManager;
|
|
private readonly bool $inShare;
|
|
private int $storageId;
|
|
|
|
public function __construct($arguments) {
|
|
parent::__construct($arguments);
|
|
$this->aclManager = $arguments['acl_manager'];
|
|
$this->inShare = $arguments['in_share'];
|
|
$this->storageId = $arguments['storage_id'];
|
|
}
|
|
|
|
private function getACLPermissionsForPath(string $path): int {
|
|
$permissions = $this->aclManager->getACLPermissionsForPath($this->storageId, $path);
|
|
|
|
// if there is no read permissions, than deny everything
|
|
if ($this->inShare) {
|
|
$canRead = $permissions & (Constants::PERMISSION_READ + Constants::PERMISSION_SHARE);
|
|
} else {
|
|
$canRead = $permissions & Constants::PERMISSION_READ;
|
|
}
|
|
|
|
return $canRead ? $permissions : 0;
|
|
}
|
|
|
|
private function checkPermissions(string $path, int $permissions): bool {
|
|
return ($this->getACLPermissionsForPath($path) & $permissions) === $permissions;
|
|
}
|
|
|
|
public function isReadable(string $path): bool {
|
|
return $this->checkPermissions($path, Constants::PERMISSION_READ) && parent::isReadable($path);
|
|
}
|
|
|
|
public function isUpdatable(string $path): bool {
|
|
return $this->checkPermissions($path, Constants::PERMISSION_UPDATE) && parent::isUpdatable($path);
|
|
}
|
|
|
|
public function isCreatable(string $path): bool {
|
|
return $this->checkPermissions($path, Constants::PERMISSION_CREATE) && parent::isCreatable($path);
|
|
}
|
|
|
|
public function isDeletable(string $path): bool {
|
|
return $this->checkPermissions($path, Constants::PERMISSION_DELETE)
|
|
&& $this->canDeleteTree($path)
|
|
&& parent::isDeletable($path);
|
|
}
|
|
|
|
public function isSharable(string $path): bool {
|
|
return $this->checkPermissions($path, Constants::PERMISSION_SHARE) && parent::isSharable($path);
|
|
}
|
|
|
|
public function getPermissions(string $path): int {
|
|
return $this->storage->getPermissions($path) & $this->getACLPermissionsForPath($path);
|
|
}
|
|
|
|
public function rename(string $source, string $target): bool {
|
|
if (str_starts_with($source, $target)) {
|
|
$part = substr($source, strlen($target));
|
|
//This is a rename of the transfer file to the original file
|
|
if (str_starts_with($part, '.ocTransferId')) {
|
|
return $this->checkPermissions($target, Constants::PERMISSION_CREATE) && parent::rename($source, $target);
|
|
}
|
|
}
|
|
|
|
$permissions = $this->file_exists($target) ? Constants::PERMISSION_UPDATE : Constants::PERMISSION_CREATE;
|
|
$sourceParent = dirname($source);
|
|
if ($sourceParent === '.') {
|
|
$sourceParent = '';
|
|
}
|
|
|
|
$targetParent = dirname($target);
|
|
if ($targetParent === '.') {
|
|
$targetParent = '';
|
|
}
|
|
|
|
return ($sourceParent === $targetParent
|
|
|| $this->checkPermissions($sourceParent, Constants::PERMISSION_DELETE))
|
|
&& $this->checkPermissions($source, Constants::PERMISSION_UPDATE & Constants::PERMISSION_READ)
|
|
&& $this->checkPermissions($target, $permissions)
|
|
&& parent::rename($source, $target);
|
|
}
|
|
|
|
public function opendir(string $path) {
|
|
if (!$this->checkPermissions($path, Constants::PERMISSION_READ)) {
|
|
return false;
|
|
}
|
|
|
|
$handle = parent::opendir($path);
|
|
if ($handle === false) {
|
|
return false;
|
|
}
|
|
|
|
$items = [];
|
|
while (($file = readdir($handle)) !== false) {
|
|
if ($file !== '.' && $file !== '..') {
|
|
if ($this->checkPermissions(trim($path . '/' . $file, '/'), Constants::PERMISSION_READ)) {
|
|
$items[] = $file;
|
|
}
|
|
}
|
|
}
|
|
|
|
return IteratorDirectory::wrap($items);
|
|
}
|
|
|
|
public function copy(string $source, string $target): bool {
|
|
$permissions = $this->file_exists($target) ? Constants::PERMISSION_UPDATE : Constants::PERMISSION_CREATE;
|
|
return $this->checkPermissions($target, $permissions)
|
|
&& $this->checkPermissions($source, Constants::PERMISSION_READ)
|
|
&& parent::copy($source, $target);
|
|
}
|
|
|
|
public function touch(string $path, ?int $mtime = null): bool {
|
|
$permissions = $this->file_exists($path) ? Constants::PERMISSION_UPDATE : Constants::PERMISSION_CREATE;
|
|
return $this->checkPermissions($path, $permissions) && parent::touch($path, $mtime);
|
|
}
|
|
|
|
public function mkdir(string $path): bool {
|
|
return $this->checkPermissions($path, Constants::PERMISSION_CREATE) && parent::mkdir($path);
|
|
}
|
|
|
|
public function rmdir(string $path): bool {
|
|
return $this->checkPermissions($path, Constants::PERMISSION_DELETE)
|
|
&& $this->canDeleteTree($path)
|
|
&& parent::rmdir($path);
|
|
}
|
|
|
|
public function unlink(string $path): bool {
|
|
return $this->checkPermissions($path, Constants::PERMISSION_DELETE)
|
|
&& $this->canDeleteTree($path)
|
|
&& parent::unlink($path);
|
|
}
|
|
|
|
/**
|
|
* When deleting we need to ensure that there is no file inside the folder being deleted that misses delete permissions
|
|
* This check is fairly expensive so we only do it for the actual delete and not metadata operations
|
|
*/
|
|
private function canDeleteTree(string $path): int {
|
|
return $this->aclManager->getPermissionsForTree($this->storageId, $path) & Constants::PERMISSION_DELETE;
|
|
}
|
|
|
|
public function file_put_contents(string $path, mixed $data): int|float|false {
|
|
$permissions = $this->file_exists($path) ? Constants::PERMISSION_UPDATE : Constants::PERMISSION_CREATE;
|
|
return $this->checkPermissions($path, $permissions) ? parent::file_put_contents($path, $data) : false;
|
|
}
|
|
|
|
public function fopen(string $path, string $mode) {
|
|
if ($mode === 'r' or $mode === 'rb') {
|
|
$permissions = Constants::PERMISSION_READ;
|
|
} else {
|
|
$permissions = $this->file_exists($path) ? Constants::PERMISSION_UPDATE : Constants::PERMISSION_CREATE;
|
|
}
|
|
|
|
return $this->checkPermissions($path, $permissions) ? parent::fopen($path, $mode) : false;
|
|
}
|
|
|
|
public function writeStream(string $path, $stream, ?int $size = null): int {
|
|
$permissions = $this->file_exists($path) ? Constants::PERMISSION_UPDATE : Constants::PERMISSION_CREATE;
|
|
return $this->checkPermissions($path, $permissions) ? parent::writeStream($path, $stream, $size) : 0;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function getCache(string $path = '', ?IStorage $storage = null): ICache {
|
|
if (!$storage) {
|
|
$storage = $this;
|
|
}
|
|
|
|
$sourceCache = parent::getCache($path, $storage);
|
|
|
|
return new ACLCacheWrapper($sourceCache, $this->aclManager, $this->inShare);
|
|
}
|
|
|
|
public function getMetaData(string $path): ?array {
|
|
$data = parent::getMetaData($path);
|
|
|
|
if ($data && isset($data['permissions'])) {
|
|
$data['scan_permissions'] ??= $data['permissions'];
|
|
$data['permissions'] &= $this->getACLPermissionsForPath($path);
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function getScanner(string $path = '', ?IStorage $storage = null): IScanner {
|
|
if (!$storage) {
|
|
$storage = $this->storage;
|
|
}
|
|
|
|
return parent::getScanner($path, $storage);
|
|
}
|
|
|
|
public function is_dir(string $path): bool {
|
|
return $this->checkPermissions($path, Constants::PERMISSION_READ)
|
|
&& parent::is_dir($path);
|
|
}
|
|
|
|
public function is_file(string $path): bool {
|
|
return $this->checkPermissions($path, Constants::PERMISSION_READ)
|
|
&& parent::is_file($path);
|
|
}
|
|
|
|
public function stat(string $path): array|false {
|
|
if (!$this->checkPermissions($path, Constants::PERMISSION_READ)) {
|
|
return false;
|
|
}
|
|
|
|
return parent::stat($path);
|
|
}
|
|
|
|
public function filetype(string $path): string|false {
|
|
if (!$this->checkPermissions($path, Constants::PERMISSION_READ)) {
|
|
return false;
|
|
}
|
|
|
|
return parent::filetype($path);
|
|
}
|
|
|
|
public function filesize(string $path): false|int|float {
|
|
if (!$this->checkPermissions($path, Constants::PERMISSION_READ)) {
|
|
return false;
|
|
}
|
|
|
|
return parent::filesize($path);
|
|
}
|
|
|
|
public function file_exists(string $path): bool {
|
|
return $this->checkPermissions($path, Constants::PERMISSION_READ)
|
|
&& parent::file_exists($path);
|
|
}
|
|
|
|
public function filemtime(string $path): int|false {
|
|
if (!$this->checkPermissions($path, Constants::PERMISSION_READ)) {
|
|
return false;
|
|
}
|
|
|
|
return parent::filemtime($path);
|
|
}
|
|
|
|
public function file_get_contents(string $path): string|false {
|
|
if (!$this->checkPermissions($path, Constants::PERMISSION_READ)) {
|
|
return false;
|
|
}
|
|
|
|
return parent::file_get_contents($path);
|
|
}
|
|
|
|
public function getMimeType(string $path): string|false {
|
|
if (!$this->checkPermissions($path, Constants::PERMISSION_READ)) {
|
|
return false;
|
|
}
|
|
|
|
return parent::getMimeType($path);
|
|
}
|
|
|
|
public function hash(string $type, string $path, bool $raw = false): string|false {
|
|
if (!$this->checkPermissions($path, Constants::PERMISSION_READ)) {
|
|
return false;
|
|
}
|
|
|
|
return parent::hash($type, $path, $raw);
|
|
}
|
|
|
|
public function getETag(string $path): string|false {
|
|
if (!$this->checkPermissions($path, Constants::PERMISSION_READ)) {
|
|
return false;
|
|
}
|
|
|
|
return parent::getETag($path);
|
|
}
|
|
|
|
public function getDirectDownload(string $path): array|false {
|
|
if (!$this->checkPermissions($path, Constants::PERMISSION_READ)) {
|
|
return false;
|
|
}
|
|
|
|
return parent::getDirectDownload($path);
|
|
}
|
|
|
|
public function getDirectoryContent(string $directory): \Traversable {
|
|
$content = $this->getWrapperStorage()->getDirectoryContent($directory);
|
|
foreach ($content as $data) {
|
|
$data['scan_permissions'] ??= $data['permissions'];
|
|
$data['permissions'] &= $this->getACLPermissionsForPath(rtrim($directory, '/') . '/' . $data['name']);
|
|
|
|
yield $data;
|
|
}
|
|
}
|
|
}
|