Skip to content

Commit a0c1dab

Browse files
committed
chore: enhance comments in ACLPlugin
moved details to private method docblocks Signed-off-by: Josh <josh.t.richards@gmail.com>
1 parent 6b76d84 commit a0c1dab

File tree

1 file changed

+72
-70
lines changed

1 file changed

+72
-70
lines changed

β€Žlib/DAV/ACLPlugin.phpβ€Ž

Lines changed: 72 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
declare(strict_types=1);
44
/**
5-
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-FileCopyrightText: 2019-2025 Nextcloud GmbH and Nextcloud contributors
66
* SPDX-License-Identifier: AGPL-3.0-or-later
77
*/
88

@@ -31,7 +31,7 @@
3131
use Sabre\Xml\Reader;
3232

3333
/**
34-
* SabreDAV plugin for exposing and updating advanced ACL properties.
34+
* SabreDAV plugin for exposing ACL (advanced permissions) properties.
3535
*
3636
* Handles WebDAV PROPFIND and PROPPATCH events for Nextcloud Teams/Group Folders with granular access controls.
3737
*
@@ -57,7 +57,7 @@ class ACLPlugin extends ServerPlugin {
5757

5858
private ?Server $server = null;
5959
private ?IUser $user = null;
60-
/** @var array<int, bool> */
60+
/** @var array<int, bool> Folder ID => can manage ACLs */
6161
private array $canManageACLForFolder = [];
6262

6363
public function __construct(
@@ -73,7 +73,7 @@ public function __construct(
7373
public function initialize(Server $server): void {
7474
$this->server = $server;
7575

76-
// may be null for public links / federated shares; handler logic must account for this.
76+
// Note: a null user is permitted (i.e. for public links / federated shares); handler logic must account for this.
7777
$this->user = $this->userSession->getUser();
7878

7979
$this->server->on('propFind', $this->propFind(...));
@@ -87,14 +87,9 @@ public function initialize(Server $server): void {
8787
}
8888

8989
/**
90-
* Property request handlers.
91-
*
92-
* These handlers provide read-only access to ACL related information.
93-
*
94-
* In the current implementation, individual property level handlers that depend on $this->user
95-
* are expected to determine and return an appropriate safe value when $this->user is null
96-
* (e.g., for public links or federated shares).
97-
*
90+
* WebDAV PROPFIND event handler for ACL-related properties.
91+
* Provides read-only access to ACL information for the current node.
92+
* If the session is unauthenticated, safe defaults are returned.
9893
*/
9994
public function propFind(PropFind $propFind, INode $node): void {
10095
if (!$node instanceof Node) {
@@ -107,55 +102,25 @@ public function propFind(PropFind $propFind, INode $node): void {
107102
return;
108103
}
109104

110-
/*
111-
* Handler to return the direct ACL rules for a specific file or folder via a WebDAV property request.
112-
*
113-
* - Direct ACL rules are those assigned directly to a specific file or folder (i.e. regardless of inheritance)
114-
* - Admins or managers set these rules on individual nodes (files or folders).
115-
* - Rules grant/restrict permissions for specific entities (users/groups/teams) for only that exact node.
116-
*
117-
* Example: If you set a rule to allow "Group X” to write to the folder `/Documents/Reports`,
118-
* that is a direct ACL rule for `/Documents/Reports`.
119-
*
120-
* Note: Even if permission is granted directly to a child, if a parent folder does not grant read/list, the
121-
* child will remain inaccessible and invisible to the user.
122-
*
123-
* Returns an empty array if non-user sessions.
124-
*/
105+
// Handler to provide ACL rules directly assigned to the file or folder.
125106
$propFind->handle(
126107
self::ACL_LIST,
127108
fn () => $this->getDirectAclRulesForPath($fileInfo, $mount)
128109
);
129110

130-
/*
131-
* Handler to return the inherited (effective) ACL rules for a file or folder via a WebDAV property request.
132-
*
133-
* Inherited (effective) ACL rules:
134-
* - are those that apply to a file or folder because they were set on one of its parent folders.
135-
* - are not set directly on the node in question -- they "cascade down" from parent directories with
136-
* specific ACLs.
137-
* - influence the effective permissions on a node by combining the rules set on its parent directories.
138-
*
139-
* Example: If `/Documents` grants "Group Y" read access, then `/Documents/Reports/file.txt` inherits that
140-
* permission even if no direct rule exists for `/Documents/Reports/file.txt`.
141-
*
142-
* Note: Even if permission is granted directly to a child, if a parent folder does not grant read/list, the
143-
* child is inaccessible and invisible to the user.
144-
*
145-
* Returns an empty array if non-user sessions.
146-
*/
111+
// Handler to provide the ACL rules inherited from parent folders (not set directly).
147112
$propFind->handle(
148113
self::INHERITED_ACL_LIST,
149114
fn () => $this->getInheritedAclRulesForPath($fileInfo, $mount)
150115
);
151116

152-
// Handler to provide the group folder ID for the current file or folder as a WebDAV property
117+
// Handler to provide the group folder ID for the current file or folder.
153118
$propFind->handle(
154119
self::GROUP_FOLDER_ID,
155120
fn (): int => $this->folderManager->getFolderByPath($fileInfo->getPath())
156121
);
157122

158-
// Handler to provide whether ACLs are enabled for the current group folder as a WebDAV property
123+
// Handler to provide whether ACLs are enabled for the current group folder.
159124
$propFind->handle(
160125
self::ACL_ENABLED,
161126
function () use ($fileInfo): bool {
@@ -164,23 +129,23 @@ function () use ($fileInfo): bool {
164129
}
165130
);
166131

167-
// Handler to determine if the current user can manage ACLs for this group folder and return as a WebDAV property
132+
// Handler to determine and return if the current user can manage ACLs for this group folder.
168133
$propFind->handle(
169134
self::ACL_CAN_MANAGE,
170135
function () use ($fileInfo): bool {
171-
// Fail softly for non-user sessions
136+
// Gracefully handle non-user sessions
172137
if ($this->user === null) {
173138
return false;
174139
}
175140
return $this->isAdmin($this->user, $fileInfo->getPath());
176141
}
177142
);
178143

179-
// Handler to provide the effective base permissions for the current group folder as a WebDAV property
144+
// Handler to provide the effective base permissions for the current group folder.
180145
$propFind->handle(
181146
self::ACL_BASE_PERMISSION_PROPERTYNAME,
182147
function () use ($mount): int {
183-
// Fail softly for non-user sessions
148+
// Gracefully handle non-user sessions
184149
if ($this->user === null) {
185150
return Constants::PERMISSION_ALL;
186151
}
@@ -192,9 +157,8 @@ function () use ($mount): int {
192157
}
193158

194159
/**
195-
* Property update handlers.
196-
*
197-
* These handlers enable modifying ACL related configuration.
160+
* WebDAV PROPPATCH event handler for ACL-related properties.
161+
* Enables modification of ACL assignments if the user has admin rights on the current node.
198162
*/
199163
public function propPatch(string $path, PropPatch $propPatch): void {
200164
if ($this->server === null) {
@@ -219,20 +183,36 @@ public function propPatch(string $path, PropPatch $propPatch): void {
219183
return;
220184
}
221185

222-
// Only allow ACL modifications if the current user has admin rights for this group folder
186+
// Only allow if user has admin rights for this group folder
223187
if (!$this->isAdmin($this->user, $fileInfo->getPath())) {
224188
return;
225189
}
226190

227-
// Handler to process and save changes to a folder's ACL rules via a WebDAV property update
191+
// Handler to update (replace) the direct ACL rules for the specified file/folder.
228192
$propPatch->handle(
229193
self::ACL_LIST,
230194
fn (array $submittedRules) => $this->updateAclRulesForPath($submittedRules, $node, $fileInfo, $mount)
231195
);
232196
}
233197

198+
/**
199+
* Retrieves ACL rules assigned directly (not inherited) to the given file or folder.
200+
*
201+
* - For admins/managers: returns all direct rules for the node.
202+
* - For non-admins/users: returns only direct rules relevant to the user.
203+
* - For public/federated sessions: returns an empty array.
204+
*
205+
* Example: Granting "Group X" write access to `/Documents/Reports` is a direct ACL rule for that folder.
206+
*
207+
* Note: Direct rules alone do not determine effective access:
208+
* - Read/list access must be permitted by all parent folders, regardless of direct rules.
209+
* - For other permissions, direct rules take priority; missing permissions may be filled via inheritance.
210+
*
211+
* Example: If "Group X" has no read access on `/Documents`, they still can't access `/Documents/Reports`.
212+
*
213+
* @return list<Rule>|null Direct ACL rules for the file/folder (may be empty).
214+
*/
234215
private function getDirectAclRulesForPath(FileInfo $fileInfo, GroupMountPoint $mount): ?array {
235-
// Fail softly for non-user sessions
236216
if ($this->user === null) {
237217
return [];
238218
}
@@ -259,6 +239,19 @@ private function getDirectAclRulesForPath(FileInfo $fileInfo, GroupMountPoint $m
259239
return array_pop($rules);
260240
}
261241

242+
/**
243+
* Retrieves ACL rules inherited from parent folders (not set directly) for the given file or folder.
244+
*
245+
* - For admins/managers: returns all inherited rules affecting the node.
246+
* - For non-admins/users: returns only inherited rules relevant to the user.
247+
* - For public/federated sessions: returns an empty array.
248+
*
249+
* Note: Inherited rules are merged from all ancestor folders; direct rules for this node are excluded.
250+
* - Effective read/list access requires all parent folders to permit access.
251+
* - Other permissions are determined by merging inherited and direct rules separately.
252+
*
253+
* @return list<Rule> Inherited ACL rules affecting the file/folder (may be empty).
254+
*/
262255
private function getInheritedAclRulesForPath(FileInfo $fileInfo, GroupMountPoint $mount): array {
263256
// Fail softly for non-user sessions
264257
if ($this->user === null) {
@@ -354,7 +347,22 @@ private function getInheritedAclRulesForPath(FileInfo $fileInfo, GroupMountPoint
354347
}
355348

356349
/**
357-
* @throws BadRequest
350+
* Update (replace) the entire set of direct ACL rules for the given file or folder.
351+
*
352+
* This method overwrites all existing direct ACL rules for the node with a new set.
353+
* Only users with ACL management rights (admins/managers) can perform this operation.
354+
*
355+
* - All provided Rule objects are written as the new direct ACLs for this file/folder.
356+
* - Inherited ACL rules from parent folders are not modified.
357+
* - If the rules array is empty, all direct ACLs on this node are removed.
358+
* - Logs critical actions and dispatches audit events for ACL changes.
359+
*
360+
* @param Rule[] $submittedRules Array of new direct ACL Rule objects to apply.
361+
* @param $node object of file/folder being updated.
362+
* @param FileInfo $fileInfo object of file or folder being updated.
363+
* @param GroupMountPoint $mount context for storage and folder resolution.
364+
*
365+
* @throws BadRequest if the operation is invalid (e.g. user's own read access would be removed)
358366
*/
359367
private function updateAclRulesForPath(array $submittedRules, Node $node, FileInfo $fileInfo, GroupMountPoint $mount): bool {
360368
$aclRelativePath = trim($mount->getSourcePath() . '/' . $fileInfo->getInternalPath(), '/');
@@ -463,13 +471,12 @@ private function updateAclRulesForPath(array $submittedRules, Node $node, FileIn
463471
}
464472

465473
/**
466-
* Checks if the given user has admin (ACL management) rights for the group folder at the given path.
467-
*
468-
* Caches the result per folder ID for efficiency.
474+
* Checks if a user has admin (ACL management) rights for the group folder at the provided path.
475+
* Results are cached per folder for efficiency.
469476
*
470477
* @param IUser $user The user to check.
471478
* @param string $path The full path to a file or folder inside a group folder.
472-
* @return bool True if the user can manage ACLs for the group folder at the given path, false otherwise.
479+
*
473480
* @throws \OCP\Files\NotFoundException If the path does not exist or is not part of a group folder.
474481
*/
475482
private function isAdmin(IUser $user, string $path): bool {
@@ -486,17 +493,12 @@ private function isAdmin(IUser $user, string $path): bool {
486493
}
487494

488495
/**
489-
* Returns all parent directory paths for the given path, based solely on the path itself.
490-
*
491-
* The array is ordered from immediate parent upward, excluding the original path.
492-
*
493-
* Example:
494-
* getParents('a/b/c.txt') returns ['a/b', 'a']
495-
*
496-
* Note: Callers should add contextual parents (such as mount points or absolute roots) if needed.
496+
* Returns all parent directory paths of the given path, from nearest to root.
497+
* Excludes the original path itself.
498+
* E.g.: 'a/b/c.txt' β†’ ['a/b', 'a']
497499
*
498500
* @param string $path Path to a file or directory.
499-
* @return string[] Parent directory paths, from closest to furthest.
501+
* @return list<string> Parent directory paths, from closest to furthest.
500502
*/
501503
private function getParents(string $path): array {
502504
$parents = [];

0 commit comments

Comments
Β (0)