feat: Enforce effective member access on shared drive moves - #4926
taratatach wants to merge 4 commits into
Conversation
935cc3d to
41600e6
Compare
The drive PATCH handler parsed but silently ignored `permanent_delete`: the `docPatch` of `web/sharings` had no `Delete` field, so a permanent delete requested through `/sharings/drives/:id/:file-id` was a no-op returning 200. Add the `Delete` field and handle it in `applyPatch` with `fs.DestroyDirAndContent(dir, fs.EnsureErased)` for directories and `fs.DestroyFile` for files, bringing the drive route to parity with the `/files` API in a single call. Authorization needs nothing new: `guardSharedDriveRouteForMember` already requires effective PATCH access on the target, so read-only members get a 403.
Cross-stack moves involving a shared drive were executed through the direct `/files` routes with the share-interact token. That token carries all verbs on the drive content, so the member authorization enforced by `guardSharedDriveRouteForMember` on the dedicated drive routes was bypassed: a read-only member could add files to a drive or permanently delete from it. Remote errors were also mishandled: logged and swallowed, or mapped to 500 by `files.WrapVfsError`. The `client` package gains `InDrive`, which scopes a client to a shared drive: `filesPath` swaps the `/files` prefix for `/sharings/drives/<id>` on every file operation, so the stack hosting the drive authorizes each request. The move flows scope their remote clients accordingly, and `wrapRemoteErr` surfaces the remote status faithfully instead of the previous log-and-continue, with `remoteStatusTextCodes` covering the common texts. The per-file deletions inside the copy loop of `moveDirBetweenSharedDrives` are dropped: the recursive root directory deletion after the loop already covers them, and a transient deletion error no longer aborts the remaining copies.
41600e6 to
7e2fdf9
Compare
| // routed through filesPath have a drive-route equivalent (reads, creation, | ||
| // metadata patch, upload, overwrite, trash, restore, destroy). It mutates | ||
| // and returns the receiver for chaining. | ||
| func (c *Client) InDrive(driveID string) *Client { |
There was a problem hiding this comment.
why new method instead of constructor? It's always called NewRemoteClient.InDrive
There was a problem hiding this comment.
NewRemoteClient is part of the sharings package while the Client struct (which keeps hold of the drive ID) is part of the client package.
The new InDrive method allows other clients to set the driveID attribute.
There was a problem hiding this comment.
add one more param to NewRemoteClient or override?
There was a problem hiding this comment.
Then only clients created via NewRemoteClient could set the driveID attribute while it's an attribute of Client.
There was a problem hiding this comment.
it can be set also with Client struct init by any consumer, as in NewRemoteClient
There was a problem hiding this comment.
We'd need to export the attribute then. Which could be a good idea actually.
Move and copy on shared drives were authorized by a flat check on the sharing: membership plus the member's `ReadOnly` flag. With nested shared folders this is wrong in both directions — it rejects a member read-only at the drive root but read-write on a nested folder, and it never accumulates scopes along the target's path. `MoveHandler` now resolves effective access on the source and the destination through `AccessResolver.ResolveForMember`: the resolver runs on the instance hosting the target while membership is matched for the calling instance. Copy requires effective read on the source; move requires effective write on the source plus effective write/create on the destination parent. Targets outside every scope return 404, insufficient access returns 403, and file-backed-drive validation keeps its 422 by running before the effective checks. The owner of a drive keeps full access on it without resolving. A remote target cannot be resolved locally, so the local stack decides nothing there: the remote stack is the only authority and applies its own access rules. `checkSharedDrivePermission` still serves membership loading and the other callers.
A read-write member could trash or destroy the root of a shared drive via `DELETE /sharings/drives/:id/:file-id` or `DELETE /sharings/drives/:id/trash/:file-id`, taking the whole drive down, as removing the root revokes the sharing. These requests are now rejected with 403 for members; only the owner keeps them.
7e2fdf9 to
504d117
Compare
Closes #4844
Moves and copies on shared drives were authorized by a flat
membership plus
ReadOnlycheck, wrong with nested shared folders,and cross-stack moves bypassed member authorization entirely via the
share-interact token.
client.InDrivescopes a client to a shared drive, so the hosting stack authorizes
each request via
guardSharedDriveRouteForMemberMoveHandlerviaAccessResolver.ResolveForMember— 404 out ofscope, 403 on insufficient access
permanent_deleteon drive PATCH, previously a silentno-op