Skip to content

Commit dc3d8c0

Browse files
icewind1991provokateurin
authored andcommitted
fix(ACL): Add check to prevent users from revoking their own access
Signed-off-by: Robin Appelman <[email protected]> Signed-off-by: provokateurin <[email protected]>
1 parent f626d29 commit dc3d8c0

File tree

6 files changed

+66
-6
lines changed

6 files changed

+66
-6
lines changed

lib/ACL/ACLManager.php

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace OCA\GroupFolders\ACL;
1010

11+
use OCA\GroupFolders\ACL\UserMapping\IUserMappingManager;
1112
use OCA\GroupFolders\Trash\TrashManager;
1213
use OCP\Cache\CappedMemoryCache;
1314
use OCP\Constants;
@@ -22,6 +23,7 @@ class ACLManager {
2223
public function __construct(
2324
private readonly RuleManager $ruleManager,
2425
private readonly TrashManager $trashManager,
26+
private readonly IUserMappingManager $userMappingManager,
2527
private readonly LoggerInterface $logger,
2628
private readonly IUser $user,
2729
private readonly \Closure $rootFolderProvider,
@@ -85,7 +87,7 @@ private function getRelevantPaths(string $path): array {
8587
$fromTrashbin = str_starts_with($path, '__groupfolders/trash/');
8688
if ($fromTrashbin) {
8789
/* Exploded path will look like ["__groupfolders", "trash", "1", "folderName.d2345678", "rest/of/the/path.txt"] */
88-
[,,$groupFolderId,$rootTrashedItemName] = explode('/', $path, 5);
90+
[, , $groupFolderId, $rootTrashedItemName] = explode('/', $path, 5);
8991
$groupFolderId = (int)$groupFolderId;
9092
/* Remove the date part */
9193
$separatorPos = strrpos($rootTrashedItemName, '.d');
@@ -143,6 +145,20 @@ public function getACLPermissionsForPath(string $path): int {
143145
return $this->calculatePermissionsForPath($rules);
144146
}
145147

148+
/**
149+
* Check what the effective permissions would be for the current user for a path would be with a new set of rules
150+
*
151+
* @param list<Rule> $newRules
152+
*/
153+
public function testACLPermissionsForPath(string $path, array $newRules): int {
154+
$path = ltrim($path, '/');
155+
$rules = $this->getRules($this->getRelevantPaths($path));
156+
157+
$rules[$path] = $this->filterApplicableRulesToUser($newRules);
158+
159+
return $this->calculatePermissionsForPath($rules);
160+
}
161+
146162
/**
147163
* @param array<string, Rule[]> $rules list of rules per path
148164
*/
@@ -229,4 +245,25 @@ public function getPermissionsForTree(string $path): int {
229245
public function preloadRulesForFolder(string $path): void {
230246
$this->ruleManager->getRulesForFilesByParent($this->user, $this->getRootStorageId(), $path);
231247
}
248+
249+
/**
250+
* Filter a list to only the rules applicable to the current user
251+
*
252+
* @param list<Rule> $rules
253+
* @return list<Rule>
254+
*/
255+
private function filterApplicableRulesToUser(array $rules): array {
256+
$userMappings = $this->userMappingManager->getMappingsForUser($this->user);
257+
return array_values(array_filter($rules, function (Rule $rule) use ($userMappings): bool {
258+
foreach ($userMappings as $userMapping) {
259+
if (
260+
$userMapping->getType() == $rule->getUserMapping()->getType() &&
261+
$userMapping->getId() == $rule->getUserMapping()->getId()
262+
) {
263+
return true;
264+
}
265+
}
266+
return false;
267+
}));
268+
}
232269
}

lib/ACL/ACLManagerFactory.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace OCA\GroupFolders\ACL;
1010

11+
use OCA\GroupFolders\ACL\UserMapping\IUserMappingManager;
1112
use OCA\GroupFolders\Trash\TrashManager;
1213
use OCP\IAppConfig;
1314
use OCP\IUser;
@@ -19,6 +20,7 @@ public function __construct(
1920
private readonly TrashManager $trashManager,
2021
private readonly IAppConfig $config,
2122
private readonly LoggerInterface $logger,
23+
private readonly IUserMappingManager $userMappingManager,
2224
private readonly \Closure $rootFolderProvider,
2325
) {
2426
}
@@ -27,6 +29,7 @@ public function getACLManager(IUser $user, ?int $rootStorageId = null): ACLManag
2729
return new ACLManager(
2830
$this->ruleManager,
2931
$this->trashManager,
32+
$this->userMappingManager,
3033
$this->logger,
3134
$user,
3235
$this->rootFolderProvider,

lib/AppInfo/Application.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ public function register(IRegistrationContext $context): void {
217217
$c->get(TrashManager::class),
218218
$c->get(IAppConfig::class),
219219
$c->get(LoggerInterface::class),
220+
$c->get(IUserMappingManager::class),
220221
$rootFolderProvider
221222
);
222223
});

lib/DAV/ACLPlugin.php

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,19 @@
99
namespace OCA\GroupFolders\DAV;
1010

1111
use OCA\DAV\Connector\Sabre\Node;
12+
use OCA\GroupFolders\ACL\ACLManagerFactory;
1213
use OCA\GroupFolders\ACL\Rule;
1314
use OCA\GroupFolders\ACL\RuleManager;
1415
use OCA\GroupFolders\ACL\UserMapping\IUserMapping;
1516
use OCA\GroupFolders\Folder\FolderManager;
1617
use OCA\GroupFolders\Mount\GroupMountPoint;
1718
use OCP\Constants;
1819
use OCP\EventDispatcher\IEventDispatcher;
20+
use OCP\IL10N;
1921
use OCP\IUser;
2022
use OCP\IUserSession;
2123
use OCP\Log\Audit\CriticalActionPerformedEvent;
24+
use Sabre\DAV\Exception\BadRequest;
2225
use Sabre\DAV\INode;
2326
use Sabre\DAV\PropFind;
2427
use Sabre\DAV\PropPatch;
@@ -41,6 +44,8 @@ public function __construct(
4144
private readonly IUserSession $userSession,
4245
private readonly FolderManager $folderManager,
4346
private readonly IEventDispatcher $eventDispatcher,
47+
private readonly ACLManagerFactory $aclManagerFactory,
48+
private readonly IL10N $l10n,
4449
) {
4550
}
4651

@@ -56,7 +61,7 @@ private function isAdmin(string $path): bool {
5661

5762
public function initialize(Server $server): void {
5863
$this->server = $server;
59-
$this->user = $user = $this->userSession->getUser();
64+
$this->user = $this->userSession->getUser();
6065

6166
$this->server->on('propFind', $this->propFind(...));
6267
$this->server->on('propPatch', $this->propPatch(...));
@@ -192,15 +197,19 @@ public function propPatch(string $path, PropPatch $propPatch): void {
192197
return false;
193198
}
194199

200+
if ($this->user === null) {
201+
return false;
202+
}
203+
195204
$path = trim($mount->getSourcePath() . '/' . $fileInfo->getInternalPath(), '/');
196205

197206
// populate fileid in rules
198-
$rules = array_map(fn (Rule $rule): Rule => new Rule(
207+
$rules = array_values(array_map(fn (Rule $rule): Rule => new Rule(
199208
$rule->getUserMapping(),
200209
$fileInfo->getId(),
201210
$rule->getMask(),
202211
$rule->getPermissions()
203-
), $rawRules);
212+
), $rawRules));
204213

205214
$formattedRules = array_map(fn (Rule $rule): string => $rule->getUserMapping()->getType() . ' ' . $rule->getUserMapping()->getDisplayName() . ': ' . $rule->formatPermissions(), $rules);
206215
if (count($formattedRules)) {
@@ -217,6 +226,12 @@ public function propPatch(string $path, PropPatch $propPatch): void {
217226
]));
218227
}
219228

229+
$aclManager = $this->aclManagerFactory->getACLManager($this->user);
230+
$newPermissions = $aclManager->testACLPermissionsForPath($fileInfo->getPath(), $rules);
231+
if (!($newPermissions & Constants::PERMISSION_READ)) {
232+
throw new BadRequest($this->l10n->t('You can not remove your own read permission.'));
233+
}
234+
220235
$existingRules = array_reduce(
221236
$this->ruleManager->getAllRulesForPaths($mount->getNumericStorageId(), [$path]),
222237
fn (array $rules, array $rulesForPath): array => array_merge($rules, $rulesForPath),

src/client.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,8 @@ class AclDavService {
252252
} else {
253253
// Handle unexpected status codes
254254
logger.error('Unexpected status:', { responseStatus: response.status, responseStatusText: response.statusText })
255-
throw new Error(t('groupfolders', 'Unexpected status from server'))
255+
256+
throw new Error(response.xhr.responseXML?.querySelector('message')?.textContent ?? t('groupfolders', 'Unexpected status from server'))
256257
}
257258
}).catch(error => {
258259
// Handle network errors or exceptions

tests/ACL/ACLManagerTest.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use OCA\GroupFolders\ACL\Rule;
1313
use OCA\GroupFolders\ACL\RuleManager;
1414
use OCA\GroupFolders\ACL\UserMapping\IUserMapping;
15+
use OCA\GroupFolders\ACL\UserMapping\IUserMappingManager;
1516
use OCA\GroupFolders\Trash\TrashManager;
1617
use OCP\Constants;
1718
use OCP\Files\IRootFolder;
@@ -24,6 +25,7 @@
2425
class ACLManagerTest extends TestCase {
2526
private RuleManager&MockObject $ruleManager;
2627
private TrashManager&MockObject $trashManager;
28+
private IUserMappingManager&MockObject $userMappingManager;
2729
private LoggerInterface&MockObject $logger;
2830
private IUser&MockObject $user;
2931
private ACLManager $aclManager;
@@ -37,6 +39,7 @@ protected function setUp(): void {
3739
$this->user = $this->createMock(IUser::class);
3840
$this->ruleManager = $this->createMock(RuleManager::class);
3941
$this->trashManager = $this->createMock(TrashManager::class);
42+
$this->userMappingManager = $this->createMock(IUserMappingManager::class);
4043
$this->logger = $this->createMock(LoggerInterface::class);
4144
$this->aclManager = $this->getAclManager();
4245
$this->dummyMapping = $this->createMapping('dummy');
@@ -75,7 +78,7 @@ private function getAclManager(bool $perUserMerge = false): ACLManager {
7578
$rootFolder->method('getMountPoint')
7679
->willReturn($rootMountPoint);
7780

78-
return new ACLManager($this->ruleManager, $this->trashManager, $this->logger, $this->user, fn (): IRootFolder&MockObject => $rootFolder, null, $perUserMerge);
81+
return new ACLManager($this->ruleManager, $this->trashManager, $this->userMappingManager, $this->logger, $this->user, fn (): IRootFolder&MockObject => $rootFolder, null, $perUserMerge);
7982
}
8083

8184
public function testGetACLPermissionsForPathNoRules(): void {

0 commit comments

Comments
 (0)