Skip to content

Commit af38aca

Browse files
committed
docs: adr for the nested shared folder
closes linagora/twake-drive#3997
1 parent 71fd016 commit af38aca

1 file changed

Lines changed: 193 additions & 0 deletions

File tree

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
# ADR: Nested Shared Folders
2+
3+
## Status
4+
5+
Draft
6+
7+
## Summary
8+
9+
Support sharing a folder that is already inside another shared folder without
10+
turning the child folder into a restrictive boundary by default. Nested folder
11+
shares are additive: parent access still applies, and the child share can add
12+
new users or higher rights. A future `limited_access` mode can make a child
13+
folder restrictive, but that is not part of the first implementation.
14+
15+
## Context
16+
17+
Today, shared drives are treated as isolated top-level sharing boundaries. The
18+
backend prevents creating a shared drive from a folder or file that is already
19+
shared, is inside another shared drive, or contains another shared drive. This
20+
keeps permission checks simple because a file belongs to a single effective
21+
shared-drive scope.
22+
23+
The nested shared folders user story changes that model. A parent folder can be
24+
shared with one set of users, and a subfolder can be shared with additional
25+
users. Users who have access through the parent should keep access to the child
26+
folder; users added directly on the child should see only that child folder when
27+
they do not have access to the parent.
28+
29+
This is different from a Google-style limited access folder. Limited access is a
30+
separate feature where parent access stops at the child folder. We want the data
31+
model and permission resolver to leave room for that future mode, but the first
32+
iteration should implement normal nested sharing as additive inheritance.
33+
34+
### Endpoint Impact And Permission Check Complexity
35+
36+
| UI journey | Endpoint called | What to check and why | Complexity |
37+
|---|---|---|---|
38+
| Create a shared folder from a folder inside another shared folder | `POST /sharings/drives` | Check the caller can read/share the selected root and can manage sharing in the parent effective scope, because the child share adds a new direct access path. | **Medium**: current anti-nesting validation must change, and inherited access must be derived. |
39+
| Create a file share inside a shared folder | existing file sharing / sharing creation endpoints | Check the caller can read/share the file in its effective scope. The file share adds access but does not become a boundary. | **Low to Medium**: additive file shares already fit the model, but nested containment checks must not treat them as drive boundaries. |
40+
| Create a new shared folder by name | `POST /sharings/drives` | Check `POST` on the parent folder, because a real VFS folder is created before it is shared. | **Low**: mostly existing folder-create permission. |
41+
| Open parent folder containing shared child folders | `GET /sharings/drives/:id/:file-id` | Check effective `GET` through parent and additive child scopes. Parent users can still open child content unless future `limited_access` blocks inheritance. | **Medium**: listing may need to annotate child shared roots and inherited/direct access. |
42+
| Open child shared folder directly | `GET /sharings/drives/:child-id/:file-id` | Check effective `GET` for the child scope. Direct child recipients can enter through the child share even if they cannot see the parent. | **Medium**: route checks must accept direct child access and inherited parent access where applicable. |
43+
| Rename/move item inside one effective scope | `PATCH /sharings/drives/:id/:file-id` | Check effective write on the item and write/create on the destination parent. | **Medium**: source and destination may have different additive scopes. |
44+
| Move item from parent shared folder into child shared folder | `PATCH /sharings/drives/:parent-id/:file-id` or `POST /sharings/drives/move` | Check write/remove on source and write/create on destination; effective access after the move is parent plus child additive scopes. | **Medium**: destination effective access must be recomputed. |
45+
| Move item from child shared folder back to parent | `PATCH /sharings/drives/:child-id/:file-id` or `POST /sharings/drives/move` | Check write/remove on child source and write/create on parent destination; preserve direct child access only if product requires it. | **Medium**: moving can change inherited access. |
46+
| Move shared folder into another shared folder | `POST /sharings/drives/move` | Compute effective users before the move, then merge with destination inherited users and keep the higher permission. | **High**: must materialize missing direct grants to avoid losing pre-move access. |
47+
| Copy/move folder containing nested shared roots | `POST /sharings/drives/move` | Find nested shared roots under the moved folder and apply merge/preserve rules for each impacted share. | **High**: proportional to nested shared roots and needs clear copy-vs-move semantics. |
48+
| Download file through parent or child shared route | `POST /sharings/drives/:id/downloads`, then `GET /sharings/drives/:id/downloads/:secret/:name` | Check effective `GET` for the requested file and verify the route is a valid access path for that user. | **Medium**: existing broad route checks need effective-scope validation. |
49+
| Download archive from a shared folder | `POST /sharings/drives/:id/archive` | Check effective `GET` for archive entries. For v1 additive sharing, inherited parent access can include child content. | **Medium to High**: archive construction still walks content and must account for nested shared roots. |
50+
| Create share-by-link inside nested shared folder | `POST /sharings/drives/:id/permissions` | Check effective `GET` on target and link-creation rights in the effective scope. | **Medium**: current containment checks must become effective-access checks. |
51+
| List share-by-link permissions | `GET /sharings/drives/:id/permissions?ids=...` | Check each requested ID is visible through effective access. | **Medium**: batch validation needed for all IDs. |
52+
| Edit/remove share-by-link | `PATCH /sharings/drives/:id/permissions/:perm-id` or `DELETE /sharings/drives/:id/permissions/:perm-id` | Check the permission doc belongs to a scope the caller can manage. | **Medium**: direct and inherited managers may differ. |
53+
| Watch live changes in parent shared folder | `GET /sharings/drives/:id/_changes` or `GET /sharings/drives/:id/realtime` | Send events only when the user has effective access to the changed item. | **High**: path-prefix filtering must be replaced or supplemented by effective-access filtering. |
54+
| Show shared folders in sidebar / shared section | `GET /sharings/drives` | Return direct shares and enough metadata to distinguish inherited users from direct recipients. | **Medium**: UI needs both "Shared with me" and "Shared by me" behavior from the user story. |
55+
| Delete/trash shared parent folder containing nested shared folders | `DELETE /sharings/drives/:id/:file-id` or `DELETE /files/:file-id` | Find nested shared roots and decide whether deletion removes them for everyone or requires preserving direct shares. | **High**: destructive operation with revocation and orphaning risk. |
56+
| Parent access removed while child direct access remains | `GET /sharings/drives`, child routes | Recompute effective access so the user loses parent-derived access but keeps direct child access. | **Medium**: requires derived inheritance instead of copied inherited members. |
57+
| Background sync/replication | internal `io.cozy.shared` updates | Track sharing refs according to effective additive scopes and preserve direct child access. | **High**: fan-out, locking, and replication semantics become more complex. |
58+
59+
## Models Considered
60+
61+
- Yandex Disk avoids nested shared folders entirely.
62+
- Google Drive normally uses inherited, additive sharing. It supports
63+
restrictive child folders only through a special limited access feature.
64+
- SharePoint, OneDrive, and Nextcloud are closer to a full ACL model, where
65+
folders or files can have inherited and overridden permissions.
66+
67+
For Twake, the first implementation should follow the normal additive behavior:
68+
share downward by inheritance, add access on child folders, and defer
69+
restrictive limited access until the product explicitly needs that mode.
70+
71+
## Decision
72+
73+
Nested folder sharing will be additive by default.
74+
75+
An `io.cozy.sharings` document remains the sharing scope and member list. The
76+
shared root remains marked on `io.cozy.files` with `referenced_by`. For nested
77+
folder shares, effective access is derived from all applicable additive sharing
78+
scopes on the path.
79+
80+
Rules:
81+
82+
- Parent folder access applies to child folders unless a future
83+
`limited_access` mode explicitly blocks inheritance.
84+
- Child folder sharing can add users or grant higher rights.
85+
- If the same user has access through parent and child scopes, the higher right
86+
wins.
87+
- File shares are additive access only. They do not create restrictive
88+
boundaries.
89+
- Limited access is reserved as an explicit access mode for a later iteration.
90+
91+
## Solution
92+
93+
Add an explicit access mode to sharing documents:
94+
95+
```text
96+
io.cozy.sharings
97+
- access_mode: additive | limited_access
98+
```
99+
100+
The default value is `additive` for existing and newly created nested folder
101+
shares. `limited_access` is reserved for the future feature and should not be
102+
enabled by this first implementation.
103+
104+
The permission resolver should distinguish between sharing scopes and
105+
restrictive boundaries:
106+
107+
```text
108+
effectiveAccessScopes(file_or_folder)
109+
returns additive parent and child sharing scopes that apply to the item
110+
111+
nearestRestrictiveBoundary(file_or_folder)
112+
reserved for future limited_access mode
113+
114+
childSharedRootsUnder(folder)
115+
returns nested shared roots impacted by recursive operations
116+
```
117+
118+
For v1, `effectiveAccessScopes` is the main permission check. A user can access
119+
an item when they are a member of any applicable additive scope. The final
120+
permission is the highest permission found across those scopes.
121+
122+
Example:
123+
124+
```text
125+
Folder A shared with Alice as editor
126+
Folder B shared with Bob as viewer
127+
```
128+
129+
Effective access to `Folder B`:
130+
131+
```text
132+
Alice: editor, inherited from Folder A
133+
Bob: viewer, direct access from Folder B
134+
```
135+
136+
Bob should see `Folder B` in "Shared with me" without seeing `Folder A`. The
137+
owner/editor should see both `Folder A` and `Folder B` in "Shared by me" as
138+
separate shared items.
139+
140+
## Implementation Notes
141+
142+
Creation:
143+
144+
- Allow creating a folder share inside an existing shared folder.
145+
- Reject or keep out of scope any operation that tries to create a restrictive
146+
nested folder by default.
147+
- Store `access_mode = additive` explicitly or treat missing `access_mode` as
148+
`additive` for backwards compatibility.
149+
150+
Permission checks:
151+
152+
- Replace single-scope checks with an effective access resolver for nested
153+
folder content.
154+
- Show inherited users separately from direct users in the sharing dialog when
155+
the UI needs to explain why a user has access.
156+
- Do not copy inherited parent members into every child sharing during normal
157+
updates. Derive inherited access from the parent chain.
158+
159+
Move:
160+
161+
- Moving a shared folder into another shared folder should merge permissions.
162+
- Users who had effective access to the moved folder before the move must keep
163+
access after the move.
164+
- Users who have access to the destination folder gain access through the new
165+
parent inheritance.
166+
- If the same user exists in both sources with different rights, keep the higher
167+
right.
168+
- To preserve pre-move access when parent inheritance changes, compute the
169+
moved folder's effective access before the move and materialize missing users
170+
as direct grants on the moved folder sharing.
171+
172+
Future limited access:
173+
174+
- `limited_access` will be the mode that turns a child folder into a hard
175+
boundary.
176+
- When a `limited_access` boundary is encountered, parent scopes above it should
177+
stop applying.
178+
- Move, delete, archive, download, realtime, and sync flows will need additional
179+
boundary checks before enabling that mode.
180+
181+
## Consequences
182+
183+
This approach matches the user story for nested shared folders and avoids a full
184+
ACL model. It keeps file shares additive and keeps inherited parent access
185+
derived instead of copied.
186+
187+
The tradeoff is that permission checks become more complex than the current
188+
single shared-drive scope. The backend needs shared helpers for effective access
189+
resolution and for finding nested shared roots impacted by recursive operations.
190+
191+
Limited access remains possible later, but it must be implemented as an
192+
explicit mode with additional checks instead of being the default behavior of
193+
nested sharing.

0 commit comments

Comments
 (0)