Skip to content

feat: add container-specific permissions and immutable flag#272

Merged
glpatcern merged 6 commits into
cs3org:mainfrom
flash7777:feature/container-permissions-and-immutable
Jun 4, 2026
Merged

feat: add container-specific permissions and immutable flag#272
glpatcern merged 6 commits into
cs3org:mainfrom
flash7777:feature/container-permissions-and-immutable

Conversation

@flash7777

@flash7777 flash7777 commented May 27, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR adds container-specific permissions and an immutable (freeze) attribute to the CS3 storage provider API.

New fields in ResourcePermissions

  • delete_container (field 21): delete permission for containers only, independent of file delete
  • move_container (field 22): move/rename permission for containers only, independent of file move

New attribute in ResourceInfo

  • immutable (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 ProviderAPI

  • SetImmutable: 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" — delete and move apply to both equally. And there is no persistent freeze/immutable concept (locks are temporary).

See docs/proposals/container-permissions-and-immutable.md for the full proposal.

Note on field 20

Field 20 in ResourceInfo is also targeted by #190 (Status). We have commented there to coordinate. We believe immutable and status serve 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/move behavior. Existing clients continue to work unchanged.

Related

flash and others added 2 commits May 27, 2026 18:35
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>
@flash7777

Copy link
Copy Markdown
Contributor Author

Context: Why we need this

We 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 delete and move permissions apply equally to files and containers. An editor who can delete a misplaced PDF can also delete an entire department folder — there is no middle ground.

The three additions in this proposal — delete_container, move_container, and immutable — are small in scope but would be a significant improvement for real-world DMS and records management use cases. They allow administrators to protect directory structures while giving users full flexibility to work with documents.

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.

@flash7777

Copy link
Copy Markdown
Contributor Author

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
@glpatcern

Copy link
Copy Markdown
Member

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.

@flash7777

Copy link
Copy Markdown
Contributor Author

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:

  • For decomposedfs (as used by OpenCloud), users never have direct storage access, so xattr-based enforcement works reliably.
  • For EOS, the flag could potentially be mapped to EOS ACLs or the existing EOS immutable attribute. But that is indeed an implementation detail per storage driver.
  • As you said, the permission fields (delete_container, move_container) are useful independently and can be adopted without implementing the immutable RPCs at all.

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.

@flash7777

Copy link
Copy Markdown
Contributor Author

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. sys.acl), so FUSE would respect it too. Would it make sense to explore a complementary PR for the EOS driver? We don't have an EOS environment but would be happy to collaborate on the approach.

@glpatcern

Copy link
Copy Markdown
Member

Would it make sense to split this into two PRs

From CS3 Reva side, I'm happy to merge this as is - we'd put stubs for now on the immutable RPCs.

What others think in OpenCloud and ownCloud? @butonic @kobergj

@micbar

micbar commented May 29, 2026

Copy link
Copy Markdown
Member

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.
@flash7777

Copy link
Copy Markdown
Contributor Author

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:

  • File = freeze (irreversible, content fixed)
  • Container = protect (structure fixed, reversible by managers)
  • Self vs. parent rule: an object is immutable if its own OR its parent's attribute is set

Added docs/proposals/immutable-overview.md as a concise reference spec.

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.
@flash7777

Copy link
Copy Markdown
Contributor Author

Added two permission fields for controlling who may set the immutable attribute:

  • set_immutable_file (field 23): freeze files — this is irreversible, so it needs to be granted carefully
  • set_immutable_container (field 24): protect/unprotect containers — reversible, suitable for space managers

This follows the same pattern as delete/delete_container and move/move_container: separating file and container operations so roles can be configured precisely. A space manager may protect directory structures without being able to permanently freeze files, or vice versa.

flash7777 pushed a commit to flash7777/reva that referenced this pull request May 30, 2026
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).
flash7777 pushed a commit to flash7777/reva that referenced this pull request May 30, 2026
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.
@flash7777

Copy link
Copy Markdown
Contributor Author

Thanks for the approval @glpatcern! Is there anything else needed before this can be merged? Happy to address any remaining concerns.

@flash7777

Copy link
Copy Markdown
Contributor Author

Note: after your approval on May 28, we pushed two additional commits on May 30:

  1. docs: clarified immutable semantics (file=freeze vs container=protect, self vs parent rule) — documentation only
  2. feat: added set_immutable_file (field 23) and set_immutable_container (field 24) to ResourcePermissions — separates the permission to freeze files (irreversible) from the permission to protect containers (reversible)

Would appreciate a quick look at these additions before merge. The rest is unchanged from what you approved.

flash7777 pushed a commit to flash7777/reva that referenced this pull request May 30, 2026
The code references fields from cs3org/cs3apis#272 (DeleteContainer,
MoveContainer, SetImmutableFile, SetImmutableContainer, Immutable).
These will compile once go-cs3apis is regenerated after cs3apis merge.
@glpatcern

Copy link
Copy Markdown
Member

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.

@glpatcern glpatcern merged commit b7e326e into cs3org:main Jun 4, 2026
2 checks passed
flash7777 pushed a commit to flash7777/reva that referenced this pull request Jun 11, 2026
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
flash7777 pushed a commit to flash7777/reva that referenced this pull request Jun 11, 2026
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
flash7777 pushed a commit to flash7777/reva that referenced this pull request Jun 11, 2026
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).
flash7777 pushed a commit to flash7777/reva that referenced this pull request Jun 11, 2026
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).
flash7777 pushed a commit to flash7777/reva that referenced this pull request Jun 13, 2026
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).
flash7777 pushed a commit to flash7777/reva that referenced this pull request Jun 13, 2026
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).
flash7777 pushed a commit to flash7777/reva that referenced this pull request Jun 14, 2026
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
flash7777 pushed a commit to flash7777/reva that referenced this pull request Jun 14, 2026
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).
flash7777 pushed a commit to flash7777/cs3apis that referenced this pull request Jun 15, 2026
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants