Skip to content

Commit 14472cb

Browse files
authored
Merge pull request #59511 from nextcloud/public-share-only-mask-home
Only mask the permissions for the users home directory for public shares
2 parents 033e78e + 3cd3c6d commit 14472cb

File tree

8 files changed

+245
-6
lines changed

8 files changed

+245
-6
lines changed

apps/dav/appinfo/v1/publicwebdav.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* SPDX-License-Identifier: AGPL-3.0-only
77
*/
88
use OC\Files\Filesystem;
9-
use OC\Files\Storage\Wrapper\PermissionsMask;
9+
use OC\Files\Storage\Wrapper\DirPermissionsMask;
1010
use OC\Files\View;
1111
use OCA\DAV\Connector\LegacyPublicAuth;
1212
use OCA\DAV\Connector\Sabre\ServerFactory;
@@ -98,7 +98,11 @@ function (\Sabre\DAV\Server $server) use (
9898
// FIXME: should not add storage wrappers outside of preSetup, need to find a better way
9999
$previousLog = Filesystem::logWarningWhenAddingStorageWrapper(false);
100100
Filesystem::addStorageWrapper('sharePermissions', function ($mountPoint, $storage) use ($share) {
101-
return new PermissionsMask(['storage' => $storage, 'mask' => $share->getPermissions() | Constants::PERMISSION_SHARE]);
101+
return new DirPermissionsMask([
102+
'storage' => $storage,
103+
'mask' => $share->getPermissions() | Constants::PERMISSION_SHARE,
104+
'path' => 'files'
105+
]);
102106
});
103107
Filesystem::addStorageWrapper('shareOwner', function ($mountPoint, $storage) use ($share) {
104108
return new PublicOwnerWrapper(['storage' => $storage, 'owner' => $share->getShareOwner()]);

apps/files_external/lib/Lib/SessionStorageWrapper.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@
1111

1212
use OC\Files\Storage\Wrapper\PermissionsMask;
1313
use OCP\Constants;
14+
use OCP\Files\Storage\IStorage;
1415

1516
/**
1617
* Wrap Storage in PermissionsMask for session ephemeral use
1718
*/
1819
class SessionStorageWrapper extends PermissionsMask {
1920
/**
20-
* @param array $parameters ['storage' => $storage]
21+
* @param array{storage: IStorage, ...} $parameters
2122
*/
2223
public function __construct(array $parameters) {
2324
// disable sharing permission

lib/composer/composer/autoload_classmap.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1733,6 +1733,7 @@
17331733
'OC\\Files\\Cache\\StorageGlobal' => $baseDir . '/lib/private/Files/Cache/StorageGlobal.php',
17341734
'OC\\Files\\Cache\\Updater' => $baseDir . '/lib/private/Files/Cache/Updater.php',
17351735
'OC\\Files\\Cache\\Watcher' => $baseDir . '/lib/private/Files/Cache/Watcher.php',
1736+
'OC\\Files\\Cache\\Wrapper\\CacheDirPermissionsMask' => $baseDir . '/lib/private/Files/Cache/Wrapper/CacheDirPermissionsMask.php',
17361737
'OC\\Files\\Cache\\Wrapper\\CacheJail' => $baseDir . '/lib/private/Files/Cache/Wrapper/CacheJail.php',
17371738
'OC\\Files\\Cache\\Wrapper\\CachePermissionsMask' => $baseDir . '/lib/private/Files/Cache/Wrapper/CachePermissionsMask.php',
17381739
'OC\\Files\\Cache\\Wrapper\\CacheWrapper' => $baseDir . '/lib/private/Files/Cache/Wrapper/CacheWrapper.php',
@@ -1818,6 +1819,7 @@
18181819
'OC\\Files\\Storage\\StorageFactory' => $baseDir . '/lib/private/Files/Storage/StorageFactory.php',
18191820
'OC\\Files\\Storage\\Temporary' => $baseDir . '/lib/private/Files/Storage/Temporary.php',
18201821
'OC\\Files\\Storage\\Wrapper\\Availability' => $baseDir . '/lib/private/Files/Storage/Wrapper/Availability.php',
1822+
'OC\\Files\\Storage\\Wrapper\\DirPermissionsMask' => $baseDir . '/lib/private/Files/Storage/Wrapper/DirPermissionsMask.php',
18211823
'OC\\Files\\Storage\\Wrapper\\Encoding' => $baseDir . '/lib/private/Files/Storage/Wrapper/Encoding.php',
18221824
'OC\\Files\\Storage\\Wrapper\\EncodingDirectoryWrapper' => $baseDir . '/lib/private/Files/Storage/Wrapper/EncodingDirectoryWrapper.php',
18231825
'OC\\Files\\Storage\\Wrapper\\Encryption' => $baseDir . '/lib/private/Files/Storage/Wrapper/Encryption.php',

lib/composer/composer/autoload_static.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1774,6 +1774,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
17741774
'OC\\Files\\Cache\\StorageGlobal' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/StorageGlobal.php',
17751775
'OC\\Files\\Cache\\Updater' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Updater.php',
17761776
'OC\\Files\\Cache\\Watcher' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Watcher.php',
1777+
'OC\\Files\\Cache\\Wrapper\\CacheDirPermissionsMask' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Wrapper/CacheDirPermissionsMask.php',
17771778
'OC\\Files\\Cache\\Wrapper\\CacheJail' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Wrapper/CacheJail.php',
17781779
'OC\\Files\\Cache\\Wrapper\\CachePermissionsMask' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Wrapper/CachePermissionsMask.php',
17791780
'OC\\Files\\Cache\\Wrapper\\CacheWrapper' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Wrapper/CacheWrapper.php',
@@ -1859,6 +1860,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
18591860
'OC\\Files\\Storage\\StorageFactory' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/StorageFactory.php',
18601861
'OC\\Files\\Storage\\Temporary' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Temporary.php',
18611862
'OC\\Files\\Storage\\Wrapper\\Availability' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/Availability.php',
1863+
'OC\\Files\\Storage\\Wrapper\\DirPermissionsMask' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/DirPermissionsMask.php',
18621864
'OC\\Files\\Storage\\Wrapper\\Encoding' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/Encoding.php',
18631865
'OC\\Files\\Storage\\Wrapper\\EncodingDirectoryWrapper' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/EncodingDirectoryWrapper.php',
18641866
'OC\\Files\\Storage\\Wrapper\\Encryption' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/Encryption.php',
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OC\Files\Cache\Wrapper;
11+
12+
use Closure;
13+
use OCP\Files\Cache\ICache;
14+
use OCP\Files\Cache\ICacheEntry;
15+
16+
class CacheDirPermissionsMask extends CachePermissionsMask {
17+
/**
18+
* @param Closure(string $path): bool $checkPath
19+
*/
20+
public function __construct(
21+
ICache $cache,
22+
int $mask,
23+
private readonly Closure $checkPath,
24+
) {
25+
parent::__construct($cache, $mask);
26+
}
27+
28+
protected function formatCacheEntry($entry): ICacheEntry|false {
29+
$checkPath = $this->checkPath;
30+
if ($checkPath($entry['path'])) {
31+
return parent::formatCacheEntry($entry);
32+
}
33+
34+
return $entry;
35+
}
36+
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-only AND (AGPL-3.0-or-later OR AGPL-3.0-only)
8+
*/
9+
10+
namespace OC\Files\Storage\Wrapper;
11+
12+
use OC\Files\Cache\Wrapper\CacheDirPermissionsMask;
13+
use OC\Files\Storage\Storage;
14+
use OCP\Files\Cache\ICache;
15+
16+
/**
17+
* While PermissionMask can mask a whole storage this can
18+
* mask a certain directory inside a storage
19+
*/
20+
class DirPermissionsMask extends PermissionsMask {
21+
22+
/**
23+
* @var string the dir that should be masked
24+
*/
25+
private readonly string $path;
26+
27+
/**
28+
* @var int remember length
29+
*/
30+
private readonly int $pathLength;
31+
32+
/**
33+
* @param array{storage: Storage, mask: int, path: string, ...} $parameters
34+
* @psalm-suppress MoreSpecificImplementedParamType
35+
*
36+
* $storage: The storage the permissions mask should be applied on
37+
* $mask: The permission bits that should be kept, a combination of the \OCP\Constant::PERMISSION_ constants
38+
* $path: The path relative to the storage root that should be masked
39+
*/
40+
public function __construct($parameters) {
41+
parent::__construct($parameters);
42+
$this->path = rtrim((string)$parameters['path'], '/');
43+
$this->pathLength = strlen((string)$parameters['path']);
44+
}
45+
46+
protected function checkPath(string $path): bool {
47+
return $path === $this->path || substr($path, 0, $this->pathLength + 1) === $this->path . '/';
48+
}
49+
50+
public function isUpdatable($path): bool {
51+
if ($this->checkPath($path)) {
52+
return parent::isUpdatable($path);
53+
}
54+
55+
return $this->storage->isUpdatable($path);
56+
}
57+
58+
public function isCreatable($path): bool {
59+
if ($this->checkPath($path)) {
60+
return parent::isCreatable($path);
61+
}
62+
63+
return $this->storage->isCreatable($path);
64+
}
65+
66+
public function isDeletable($path): bool {
67+
if ($this->checkPath($path)) {
68+
return parent::isDeletable($path);
69+
}
70+
71+
return $this->storage->isDeletable($path);
72+
}
73+
74+
public function isSharable($path): bool {
75+
if ($this->checkPath($path)) {
76+
return parent::isSharable($path);
77+
}
78+
79+
return $this->storage->isSharable($path);
80+
}
81+
82+
public function getPermissions($path): int {
83+
if ($this->checkPath($path)) {
84+
return parent::getPermissions($path);
85+
}
86+
87+
return $this->storage->getPermissions($path);
88+
}
89+
90+
public function rename($source, $target): bool {
91+
if (!$this->isUpdatable($source)) {
92+
return false;
93+
}
94+
95+
if ($this->file_exists($target)) {
96+
if ($this->isUpdatable($target)) {
97+
return $this->storage->rename($source, $target);
98+
}
99+
} else {
100+
$parent = dirname($target);
101+
if ($parent === '.') {
102+
$parent = '';
103+
}
104+
105+
if ($this->isCreatable($parent)) {
106+
return $this->storage->rename($source, $target);
107+
}
108+
}
109+
110+
return false;
111+
}
112+
113+
public function copy($source, $target): bool {
114+
if (!$this->isReadable($source)) {
115+
return false;
116+
}
117+
118+
if ($this->file_exists($target)) {
119+
if ($this->isUpdatable($target)) {
120+
return $this->storage->copy($source, $target);
121+
}
122+
} else {
123+
$parent = dirname($target);
124+
if ($parent === '.') {
125+
$parent = '';
126+
}
127+
128+
if ($this->isCreatable($parent)) {
129+
return $this->storage->copy($source, $target);
130+
}
131+
}
132+
133+
return false;
134+
}
135+
136+
public function touch($path, $mtime = null): bool {
137+
if ($this->checkPath($path)) {
138+
return parent::touch($path);
139+
}
140+
141+
return $this->storage->touch($path);
142+
}
143+
144+
public function mkdir($path): bool {
145+
// Always allow creating the path of the dir mask.
146+
if ($path !== $this->path && $this->checkPath($path)) {
147+
return parent::mkdir($path);
148+
}
149+
150+
return $this->storage->mkdir($path);
151+
}
152+
153+
public function rmdir($path): bool {
154+
if ($this->checkPath($path)) {
155+
return parent::rmdir($path);
156+
}
157+
158+
return $this->storage->rmdir($path);
159+
}
160+
161+
public function unlink($path): bool {
162+
if ($this->checkPath($path)) {
163+
return parent::unlink($path);
164+
}
165+
166+
return $this->storage->unlink($path);
167+
}
168+
169+
public function file_put_contents($path, $data): int|float|false {
170+
if ($this->checkPath($path)) {
171+
return parent::file_put_contents($path, $data);
172+
}
173+
174+
return $this->storage->file_put_contents($path, $data);
175+
}
176+
177+
public function fopen($path, $mode) {
178+
if ($this->checkPath($path)) {
179+
return parent::fopen($path, $mode);
180+
}
181+
182+
return $this->storage->fopen($path, $mode);
183+
}
184+
185+
public function getCache($path = '', $storage = null): ICache {
186+
if (!$storage) {
187+
$storage = $this;
188+
}
189+
190+
$sourceCache = $this->storage->getCache($path, $storage);
191+
return new CacheDirPermissionsMask($sourceCache, $this->mask, $this->checkPath(...));
192+
}
193+
}

lib/private/Files/Storage/Wrapper/PermissionsMask.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
namespace OC\Files\Storage\Wrapper;
99

1010
use OC\Files\Cache\Wrapper\CachePermissionsMask;
11+
use OC\Files\Storage\Storage;
1112
use OCP\Constants;
1213
use OCP\Files\Cache\ICache;
1314
use OCP\Files\Cache\IScanner;
@@ -24,10 +25,10 @@ class PermissionsMask extends Wrapper {
2425
/**
2526
* @var int the permissions bits we want to keep
2627
*/
27-
private $mask;
28+
protected readonly int $mask;
2829

2930
/**
30-
* @param array $parameters ['storage' => $storage, 'mask' => $mask]
31+
* @param array{storage: Storage, mask: int, ...} $parameters
3132
*
3233
* $storage: The storage the permissions mask should be applied on
3334
* $mask: The permission bits that should be kept, a combination of the \OCP\Constant::PERMISSION_ constants

lib/private/Files/Storage/Wrapper/Wrapper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class Wrapper implements Storage, ILockingStorage, IWriteStreamStorage {
3939
public ?IUpdater $updater = null;
4040

4141
/**
42-
* @param array{storage: Storage} $parameters
42+
* @param array{storage: Storage, ...} $parameters
4343
*/
4444
public function __construct(array $parameters) {
4545
$this->storage = $parameters['storage'];

0 commit comments

Comments
 (0)