feat: add container-specific permissions and immutable flag#272
Conversation
Add two new permission fields to ResourcePermissions: - delete_container (field 21): controls whether containers/directories can be deleted, independent of the file delete permission - move_container (field 22): controls whether containers/directories can be moved or renamed, independent of the file move permission Add immutable field (field 20) to ResourceInfo: - When set, prevents modification, deletion, moving or renaming - For containers, also prevents creation of new children - Existing non-immutable children can still be modified These additions enable DMS use cases such as: - File plan (Aktenplan) structure protection - Retention/legal hold at the resource level - Granular permission control separating file and directory operations The new permission fields are backward compatible: when not explicitly set, implementations should fall back to the existing delete/move permissions for containers. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add proposal document explaining the motivation, specification, and backward compatibility for the new delete_container, move_container, and immutable fields. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Context: Why we need thisWe are currently developing an EDMS (Electronic Document Management System) layer on top of OpenCloud/Reva for German municipalities and public administration (Kommunalverwaltungen). In this domain, file plans (Aktenpläne) are a fundamental concept — rigid, hierarchical directory structures (typically 4 levels deep) that must remain stable over years or even decades. Users work within this structure daily: they create, edit, and manage documents, but the structure itself must not be accidentally (or intentionally) altered by regular users. Today, the CS3 permission model offers no way to express this. The The three additions in this proposal — We believe this benefits not just our EDMS project, but any CS3-based deployment where directory structures carry organizational meaning — universities, research institutions, enterprises, and public sector organizations alike. We are happy to contribute the implementation work in Reva (see cs3org/reva#5628) and would appreciate feedback from the community on the proposed API design. |
|
Quick note: The commits mention "Claude" as co-author — this just means we used Claude Code (AI coding assistant) as tooling support during development. The proposal and its rationale are entirely ours, based on real requirements from our EDMS project. Happy to discuss any aspect of it. |
- Add immutable field (20) to ResourceInfo as persistent xattr attribute - Add SetImmutable/UnsetImmutable RPCs to ProviderAPI - Update proposal to clarify distinction between attribute (immutable) and action (SetImmutable), and how it differs from locks and status
|
Hi @flash7777, thanks for the contribution, I can see the idea is genuine even if the text is produced by Claude. I do think this makes very much sense. The only issue I can see in terms of implementations is how can a storage provider honor the immutable flag, for the cases (such as how we deploy Reva at CERN) where users have direct access to the storage. But ok this is kind of an implementation detail, the extended permission model could already be used without implementing the immutable RPCs. |
|
Thanks for the quick feedback! Good point about the immutable flag and direct storage access. You are right — if users can bypass Reva and access the storage directly (e.g. via EOS FUSE mount), an xattr-based immutable flag alone cannot be enforced. A few thoughts:
Would it make sense to split this into two PRs — one for the permission fields only, and a separate one for immutable? That way the permissions can move forward without being blocked by the immutable discussion. |
|
One more thought on the direct-access question: regardless of the enforcement layer, the immutable state has to live somewhere. A separate database or metastore would add complexity and synchronization issues. Storing it as an xattr directly on the resource (as Reva already does for locks, ACLs, etc.) seems like the natural fit. For EOS with FUSE access, the storage driver could map the immutable flag to native EOS attributes (e.g. |
|
I see that the community is growing and more use cases are evolving. I see no disadvantages with this change, so let’s go for it. |
Refine the immutable field comment to distinguish: - File (freeze): final, irreversible, no modification - Container (protect): structure fixed, reversible by managers - Self vs. parent rule: object is immutable if own or parent attribute is set Add immutable-overview.md as reference specification. No structural changes to the proto definition.
|
Pushed a documentation-only update to clarify the immutable semantics based on our EDMS experience. No structural changes to the proto — just refined comments. Key clarifications:
Added |
Add two permission fields to control who may set the immutable attribute, separated by resource type: - set_immutable_file (field 23): freeze files (irreversible) - set_immutable_container (field 24): protect/unprotect containers (reversible by managers) This separation allows roles to grant container protection rights without granting the ability to irreversibly freeze files.
|
Added two permission fields for controlling who may set the immutable attribute:
This follows the same pattern as |
Add xattr-based immutable (freeze/protect) support: Storage: - New xattr: user.oc.immutable - ImmutableState enum: None, Protected (parent), Frozen (self) Node methods: - IsImmutable(): check self attribute - GetImmutableState(): effective state (self or parent) - FreezeFile(): set immutable on file (irreversible) - ProtectContainer(): set immutable on container (reversible) - UnprotectContainer(): remove immutable from container Handler checks (Delete, Move, CreateDir, Upload): - Frozen/protected resources cannot be deleted or moved - Protected containers reject new children and modifications - Frozen files reject overwrite TODO markers for delete_container/move_container (cs3org/cs3apis#272).
Port cs3org/cs3apis#272 via forked go-cs3apis with new fields: - DeleteContainer, MoveContainer (ResourcePermissions fields 21-22) - SetImmutableFile, SetImmutableContainer (fields 23-24) - Immutable (ResourceInfo field 20) Changes: - go.mod: replace cs3org/go-cs3apis with flash7777/go-cs3apis fork - role.go: Editor/SpaceEditor/Manager/Coowner get DeleteContainer and MoveContainer; Manager/Coowner get SetImmutable permissions - decomposedfs.go: replace TODO markers with real DeleteContainer and MoveContainer checks in Delete/Move handlers - grants.go: ACL encoding/decoding for +dc/!dc and +mc/!mc - tests: add DeleteContainer to permission mocks where dirs are deleted Directory Delete/Move now requires explicit DeleteContainer/MoveContainer permission. This is a breaking change — existing roles have been updated.
|
Thanks for the approval @glpatcern! Is there anything else needed before this can be merged? Happy to address any remaining concerns. |
|
Note: after your approval on May 28, we pushed two additional commits on May 30:
Would appreciate a quick look at these additions before merge. The rest is unchanged from what you approved. |
The code references fields from cs3org/cs3apis#272 (DeleteContainer, MoveContainer, SetImmutableFile, SetImmutableContainer, Immutable). These will compile once go-cs3apis is regenerated after cs3apis merge.
|
Thanks @flash7777 and apologies for the late reply. I think it's all ok, the only concern is on the (nowadays AI-generated) doc, as all the rest of the APIs rely on the inline docs to generate https://buf.build/cs3org-buf/cs3apis - but this can be arranged at a later stage. I'll merge the PR. |
Update to the latest go-cs3apis which includes three upstream changes: 1. cs3org/cs3apis#272 — container-specific permissions + immutable RPCs 2. cs3org/cs3apis#273 — OCM share state changes 3. Labels API moved from StorageProvider to cs3/labels/v1beta1 Labels API migration: Before (old cs3apis): ┌──────────┐ AddLabel/RemoveLabel ┌─────────────────┐ │ opencloud├──────────────────────────►│ Gateway │ │ (graph) │ provider.AddLabelReq │ │ └──────────┘ │ ┌─────────────┐ │ │ │ find(ref) │ │ │ └──────┬──────┘ │ └─────────┼────────┘ │ c.AddLabel() ┌─────────▼────────┐ │ StorageProvider │ │ (ProviderAPI) │ │ → Storage.Add... │ └──────────────────┘ After (new cs3apis): ┌──────────┐ AddLabel/RemoveLabel ┌─────────────────┐ │ opencloud├──────────────────────────►│ Gateway │ │ (graph) │ labels.AddLabelReq │ │ └──────────┘ │ returns │ │ UNIMPLEMENTED │ │ (TODO: wire to │ │ LabelsAPI svc) │ └─────────────────┘ ╳ not yet wired ┌─────────────────┐ │ StorageProvider │ │ (LabelsAPI svc) │ │ → Storage.Add.. │ └─────────────────┘ The StorageProvider now registers as LabelsAPIServer (in addition to ProviderAPIServer and SpacesAPIServer), so AddLabel/RemoveLabel still work at the storage level. The Gateway needs to be wired to call the LabelsAPI service on the StorageProvider — this requires extending the gateway client pool to support LabelsAPIClient. For now, Gateway returns CODE_UNIMPLEMENTED for Labels calls. This temporarily breaks favorites in opencloud until the wiring is completed (separate PR). Changes: - go.mod: bump go-cs3apis - StorageProvider: register as LabelsAPIServer, implement AddLabel/ RemoveLabel/ListLabels/ListResourcesForLabel - Gateway: Labels stubs (UNIMPLEMENTED, TODO: wire to LabelsAPI) - Add SetImmutable/UnsetImmutable stubs to all ProviderAPI implementations - Regenerate mocks
Update to the latest go-cs3apis which includes three upstream changes: 1. cs3org/cs3apis#272 — container-specific permissions + immutable RPCs 2. cs3org/cs3apis#273 — OCM share state changes 3. Labels API moved from StorageProvider to cs3/labels/v1beta1 Labels API migration: Before: opencloud -> Gateway -> StorageProvider (ProviderAPI.AddLabel) After: opencloud -> Gateway -> StorageProvider (LabelsAPI.AddLabel) The StorageProvider now registers as LabelsAPIServer in addition to ProviderAPIServer and SpacesAPIServer. The Gateway routes Labels calls to the StorageProvider via a new LabelsAPIClient, using the same GRPC connection pool. Changes: - go.mod: bump go-cs3apis - StorageProvider: register as LabelsAPIServer, implement AddLabel/RemoveLabel via Storage FS interface - Gateway: route AddLabel/RemoveLabel to StorageProvider's LabelsAPI - Pool: add LabelsProviderSelector and GetLabelsProviderServiceClient - Add SetImmutable/UnsetImmutable to all ProviderAPI implementations - Regenerate mocks
Add two features to the decomposedfs storage driver: 1. Container-specific permissions (cs3org/cs3apis#272): - DeleteContainer/MoveContainer checks in Delete/Move handlers - ACL encoding: +dc/!dc, +mc/!mc (with substring collision fix) - Roles updated: Editor/Manager/Coowner get DeleteContainer/MoveContainer - SetImmutableFile/SetImmutableContainer permissions on Manager/Coowner 2. Immutable attribute (freeze/protect): - xattr: user.oc.immutable - File (freeze): content fixed, irreversible - Container (protect): structure fixed, reversible by managers - Self vs parent rule: ImmutableState = Frozen/Protected/None - Node methods: FreezeFile, ProtectContainer, UnprotectContainer - Storage interface: SetImmutable/UnsetImmutable with permission checks - GRPC handlers: storageprovider + gateway pass-through - Handler checks: Delete, Move, CreateDir, Upload - Stat: ResourceInfo.Immutable + Opaque immutable-state - WebDAV: oc:immutable property + D/NV strip from oc:permissions - OwnerPermissions/AddPermissions updated Tests: 21 new (node, handler, grants ACL), all pass. 2 pre-existing failures (UpdateGrant/DenyGrant ACL round-trip).
Add two features to the decomposedfs storage driver: 1. Container-specific permissions (cs3org/cs3apis#272): - DeleteContainer/MoveContainer checks in Delete/Move handlers - ACL encoding: +dc/!dc, +mc/!mc (with substring collision fix) - Roles updated: Editor/Manager/Coowner get DeleteContainer/MoveContainer - SetImmutableFile/SetImmutableContainer permissions on Manager/Coowner 2. Immutable attribute (freeze/protect): - xattr: user.oc.immutable - File (freeze): content fixed, irreversible - Container (protect): structure fixed, reversible by managers - Self vs parent rule: ImmutableState = Frozen/Protected/None - Node methods: FreezeFile, ProtectContainer, UnprotectContainer - Storage interface: SetImmutable/UnsetImmutable with permission checks - GRPC handlers: storageprovider + gateway pass-through - Handler checks: Delete, Move, CreateDir, Upload - Stat: ResourceInfo.Immutable + Opaque immutable-state - WebDAV: oc:immutable property + D/NV strip from oc:permissions - OwnerPermissions/AddPermissions updated Tests: 21 new (node, handler, grants ACL), all pass. 2 pre-existing failures (UpdateGrant/DenyGrant ACL round-trip).
Add two features to the decomposedfs storage driver: 1. Container-specific permissions (cs3org/cs3apis#272): - DeleteContainer/MoveContainer checks in Delete/Move handlers - ACL encoding: +dc/!dc, +mc/!mc (with substring collision fix) - Roles updated: Editor/Manager/Coowner get DeleteContainer/MoveContainer - SetImmutableFile/SetImmutableContainer permissions on Manager/Coowner 2. Immutable attribute (freeze/protect): - xattr: user.oc.immutable - File (freeze): content fixed, irreversible - Container (protect): structure fixed, reversible by managers - Self vs parent rule: ImmutableState = Frozen/Protected/None - Node methods: FreezeFile, ProtectContainer, UnprotectContainer - Storage interface: SetImmutable/UnsetImmutable with permission checks - GRPC handlers: storageprovider + gateway pass-through - Handler checks: Delete, Move, CreateDir, Upload - Stat: ResourceInfo.Immutable + Opaque immutable-state - WebDAV: oc:immutable property + D/NV strip from oc:permissions - OwnerPermissions/AddPermissions updated Tests: 21 new (node, handler, grants ACL), all pass. 2 pre-existing failures (UpdateGrant/DenyGrant ACL round-trip).
Add two features to the decomposedfs storage driver: 1. Container-specific permissions (cs3org/cs3apis#272): - DeleteContainer/MoveContainer checks in Delete/Move handlers - ACL encoding: +dc/!dc, +mc/!mc (with substring collision fix) - Roles updated: Editor/Manager/Coowner get DeleteContainer/MoveContainer - SetImmutableFile/SetImmutableContainer permissions on Manager/Coowner 2. Immutable attribute (freeze/protect): - xattr: user.oc.immutable - File (freeze): content fixed, irreversible - Container (protect): structure fixed, reversible by managers - Self vs parent rule: ImmutableState = Frozen/Protected/None - Node methods: FreezeFile, ProtectContainer, UnprotectContainer - Storage interface: SetImmutable/UnsetImmutable with permission checks - GRPC handlers: storageprovider + gateway pass-through - Handler checks: Delete, Move, CreateDir, Upload - Stat: ResourceInfo.Immutable + Opaque immutable-state - WebDAV: oc:immutable property + D/NV strip from oc:permissions - OwnerPermissions/AddPermissions updated Tests: 21 new (node, handler, grants ACL), all pass. 2 pre-existing failures (UpdateGrant/DenyGrant ACL round-trip).
Update to the latest go-cs3apis which includes three upstream changes: 1. cs3org/cs3apis#272 — container-specific permissions + immutable RPCs 2. cs3org/cs3apis#273 — OCM share state changes 3. Labels API moved from StorageProvider to cs3/labels/v1beta1 Labels API migration: Before: opencloud -> Gateway -> StorageProvider (ProviderAPI.AddLabel) After: opencloud -> Gateway -> StorageProvider (LabelsAPI.AddLabel) The StorageProvider now registers as LabelsAPIServer in addition to ProviderAPIServer and SpacesAPIServer. The Gateway routes Labels calls to the StorageProvider via a new LabelsAPIClient, using the same GRPC connection pool. Changes: - go.mod: bump go-cs3apis - StorageProvider: register as LabelsAPIServer, implement AddLabel/RemoveLabel via Storage FS interface - Gateway: route AddLabel/RemoveLabel to StorageProvider's LabelsAPI - Pool: add LabelsProviderSelector and GetLabelsProviderServiceClient - Add SetImmutable/UnsetImmutable to all ProviderAPI implementations - Regenerate mocks
Add two features to the decomposedfs storage driver: 1. Container-specific permissions (cs3org/cs3apis#272): - DeleteContainer/MoveContainer checks in Delete/Move handlers - ACL encoding: +dc/!dc, +mc/!mc (with substring collision fix) - Roles updated: Editor/Manager/Coowner get DeleteContainer/MoveContainer - SetImmutableFile/SetImmutableContainer permissions on Manager/Coowner 2. Immutable attribute (freeze/protect): - xattr: user.oc.immutable - File (freeze): content fixed, irreversible - Container (protect): structure fixed, reversible by managers - Self vs parent rule: ImmutableState = Frozen/Protected/None - Node methods: FreezeFile, ProtectContainer, UnprotectContainer - Storage interface: SetImmutable/UnsetImmutable with permission checks - GRPC handlers: storageprovider + gateway pass-through - Handler checks: Delete, Move, CreateDir, Upload - Stat: ResourceInfo.Immutable + Opaque immutable-state - WebDAV: oc:immutable property + D/NV strip from oc:permissions - OwnerPermissions/AddPermissions updated Tests: 21 new (node, handler, grants ACL), all pass. 2 pre-existing failures (UpdateGrant/DenyGrant ACL round-trip).
Follow-up to cs3org#272 which added SetImmutable/UnsetImmutable to the StorageProvider. This exposes the same RPCs on the Gateway so that clients (like the Graph API in OpenCloud) can call them through the gateway without needing direct StorageProvider access. - SetImmutable: freeze files (irreversible) or protect containers (reversible) - UnsetImmutable: remove protection from containers (frozen files stay frozen)
Summary
This PR adds container-specific permissions and an immutable (freeze) attribute to the CS3 storage provider API.
New fields in
ResourcePermissionsdelete_container(field 21): delete permission for containers only, independent of filedeletemove_container(field 22): move/rename permission for containers only, independent of filemoveNew attribute in
ResourceInfoimmutable(field 20): persistent boolean attribute (xattr) — when true, resource cannot be modified/deleted/moved/renamed. For containers, also prevents creation of new children.New RPCs in
ProviderAPISetImmutable: freezes a resource (manager/admin only)UnsetImmutable: unfreezes a resource (manager/admin only)Motivation
We are developing an EDMS layer for German municipalities on top of OpenCloud/Reva. Core requirement: protecting file plan (Aktenplan) directory structures while allowing users to work freely with documents inside them.
The current API cannot express "users may delete files but not directories" —
deleteandmoveapply to both equally. And there is no persistent freeze/immutable concept (locks are temporary).See
docs/proposals/container-permissions-and-immutable.mdfor the full proposal.Note on field 20
Field 20 in
ResourceInfois also targeted by #190 (Status). We have commented there to coordinate. We believeimmutableandstatusserve different purposes (persistent admin attribute vs. transient processing state) and deserve separate fields.Backward compatibility
All changes are additive. When new permission fields are not set, implementations fall back to existing
delete/movebehavior. Existing clients continue to work unchanged.Related