Skip to content

feat: go-cs3apis update + immutable attribute + container permissions#676

Open
flash7777 wants to merge 11 commits into
opencloud-eu:mainfrom
flash7777:feature/immutable-decomposedfs
Open

feat: go-cs3apis update + immutable attribute + container permissions#676
flash7777 wants to merge 11 commits into
opencloud-eu:mainfrom
flash7777:feature/immutable-decomposedfs

Conversation

@flash7777

Copy link
Copy Markdown

Summary

Two features + go-cs3apis update in one PR:

1. go-cs3apis update (Jun 2026)

Bumps go-cs3apis to include cs3org/cs3apis#272 and Labels API migration.

Before:  opencloud -> Gateway -> StorageProvider (ProviderAPI.AddLabel)
After:   opencloud -> Gateway -> StorageProvider (LabelsAPI.AddLabel)

StorageProvider registers as LabelsAPIServer. Gateway routes via new LabelsAPIClient.

2. Immutable attribute (freeze/protect)

Persistent xattr (user.oc.immutable) on resources:

  • File (freeze): content fixed, irreversible
  • Container (protect): structure fixed, reversible by managers
  • Self vs parent rule: Frozen / Protected / None

3. Container-specific permissions

DeleteContainer / MoveContainer separate file and directory operations.
SetImmutableFile / SetImmutableContainer control who can freeze/protect.

Changes

  • go.mod: bump go-cs3apis
  • Labels: StorageProvider as LabelsAPIServer, Gateway wiring via pool
  • decomposedfs: immutable xattr, handler checks (Delete/Move/CreateDir/Upload)
  • Node: FreezeFile, ProtectContainer, UnprotectContainer, GetImmutableState
  • Roles: DeleteContainer/MoveContainer on Editor+, SetImmutable on Manager+
  • ACL: +dc/!dc, +mc/!mc, +if/+ic encoding
  • WebDAV: oc:immutable property, D/NV strip
  • GRPC: SetImmutable/UnsetImmutable handlers
  • 21 new tests

Test plan

  • Full build passes
  • 21 new tests pass (node, handler, grants ACL)
  • CI pipeline
  • Labels (favorites) end-to-end
  • Immutable: set on dir, verify children protected

@flash7777

Copy link
Copy Markdown
Author

Note: This PR adds the storage-level infrastructure for immutable resources. Follow-up PRs for the Graph API layer (new LibreGraph actions for container/delete, container/move, immutable/file/set, immutable/container/set) and the web frontend (frozen/protected icons, canBeDeleted/canRename integration) are ready on separate branches and will be submitted after this PR is merged.

@flash7777 flash7777 force-pushed the feature/immutable-decomposedfs branch 3 times, most recently from 8f0f8f7 to 8f0f07b Compare June 13, 2026 16:48
@flash7777

Copy link
Copy Markdown
Author

Update: unit tests added + OpenCloud compatibility note

New commit: role permission tests

Added unit tests verifying that container-specific permissions are correctly assigned to roles:

  • Editor/SpaceEditor: DeleteContainer + MoveContainer (no SetImmutable)
  • Manager/Coowner: all new permissions including SetImmutableFile + SetImmutableContainer
  • Viewer: none of the new permissions
  • SufficientPermissions: Manager >= Editor, Editor < Manager

Required OpenCloud change after merge

After merging this PR, OpenCloud's services/graph/pkg/service/v0/follow.go must be updated because the Labels API moved from provider to labels package:

// Add import:
labels "github.com/cs3org/go-cs3apis/cs3/labels/v1beta1"

// Change (line ~64):
req := &labels.AddLabelRequest{...}    // was: provider.AddLabelRequest

// Change (line ~133):
req := &labels.RemoveLabelRequest{...} // was: provider.RemoveLabelRequest

Without this change, OpenCloud will fail to compile after updating go-cs3apis.

flash added 3 commits June 14, 2026 18:31
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).
Verify that new permissions (DeleteContainer, MoveContainer,
SetImmutableFile, SetImmutableContainer) are correctly assigned:
- Editor/SpaceEditor: DeleteContainer + MoveContainer (no SetImmutable)
- Manager/Coowner: all new permissions
- Viewer: none of the new permissions
- SufficientPermissions: Manager >= Editor, Editor < Manager
@flash7777 flash7777 force-pushed the feature/immutable-decomposedfs branch from db0affa to 8fa78d0 Compare June 14, 2026 16:34
flash7777 pushed a commit to flash7777/opencloud that referenced this pull request Jun 14, 2026
…ources

Three new Graph API endpoints on drive items:

  POST   /drives/{driveID}/items/{itemID}/freeze    - freeze a file (irreversible)
  POST   /drives/{driveID}/items/{itemID}/protect   - protect a directory (reversible)
  DELETE /drives/{driveID}/items/{itemID}/protect    - unprotect a directory

Semantics:
- freeze: sets immutable on files, cannot be undone, client must confirm
- protect: sets immutable on directories, can be reversed by managers
- unprotect: removes immutable from directories

Each endpoint validates the resource type (file vs directory) and returns
appropriate errors for type mismatches, permission denied, and not found.

Depends on: opencloud-eu/reva#676 (SetImmutable/UnsetImmutable RPCs)
flash7777 pushed a commit to flash7777/opencloud that referenced this pull request Jun 14, 2026
…ources

Three new Graph API endpoints on drive items:

  POST   /drives/{driveID}/items/{itemID}/freeze    - freeze a file (irreversible)
  POST   /drives/{driveID}/items/{itemID}/protect   - protect a directory (reversible)
  DELETE /drives/{driveID}/items/{itemID}/protect    - unprotect a directory

Semantics:
- freeze: sets immutable on files, cannot be undone, client must confirm
- protect: sets immutable on directories, can be reversed by managers
- unprotect: removes immutable from directories

Each endpoint validates the resource type (file vs directory) and returns
appropriate errors for type mismatches, permission denied, and not found.

Depends on: opencloud-eu/reva#676 (SetImmutable/UnsetImmutable RPCs)
flash and others added 8 commits June 14, 2026 23:07
A directory with IsImmutable=true is protected, not frozen.
Only files can be frozen. Previously GetImmutableState returned
ImmutableFrozen for any node with IsImmutable=true regardless of type.

Now:
- File + IsImmutable=true → ImmutableFrozen
- Directory + IsImmutable=true → ImmutableProtected (self-protected)
- Child of immutable parent → ImmutableProtected (inherited)
- Normal → ImmutableNone
- ImmutableShielded: new state for children of protected folders
- Editor roles: SetImmutableFile=true (can freeze files)
- PROPFIND: opaque immutable-state takes priority over md.Immutable
- GetImmutableState: dir=protected, file=frozen, child=shielded
- go.mod: replace go-cs3apis for gateway SetImmutable
- Any immutable-state (protected, shielded, frozen) strips permissions
- Added CK (create children) to stripped permissions
- Fixes UI showing delete/cut/create-folder on shielded items
Shielded items (inherited protection) can still have new children.
Only self-protected and frozen items lose create-children permission.
The new permissions (DeleteContainer, MoveContainer, SetImmutableFile,
SetImmutableContainer) were not encoded/decoded in the ACE xattr format,
causing them to be lost on grant roundtrip. This broke permission tests
for Manager and Editor roles on project spaces.

New ACE letters: D (DeleteContainer), m (MoveContainer),
i (SetImmutableFile), I (SetImmutableContainer).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GetImmutableState() called Parent() for every child in a directory
listing, creating redundant xattr reads (1508 reads for 1508 files).
Now ListFolder computes the parent's immutable state once and passes
it via context, eliminating all per-child Parent() lookups.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reva deletes directories by moving the entire subtree to trash.
Without this check, a frozen file inside a normal folder would be
silently trashed, bypassing immutability guarantees.

The new hasImmutableDescendant() scan runs only on directory deletes
and aborts on first match, so the performance impact is minimal.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a user's ACE grant lacks SetImmutableFile/SetImmutableContainer
(e.g. old grants written before the i/I ACE letters), Reva now checks
the global "Drives.ManageImmutable" permission as fallback. Users with
the global Admin, SpaceAdmin, or Manager role can protect/unprotect/freeze
on any space without needing the space-level Manager role.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

1 participant