Skip to content

feat: immutable attribute + container-specific permissions#697

Open
flash7777 wants to merge 12 commits into
opencloud-eu:mainfrom
flash7777:feat/immutable-clean
Open

feat: immutable attribute + container-specific permissions#697
flash7777 wants to merge 12 commits into
opencloud-eu:mainfrom
flash7777:feat/immutable-clean

Conversation

@flash7777

Copy link
Copy Markdown

Summary

Add immutable resource support with three states (frozen, protected, shielded) and container-specific permissions. Replaces #676 (which had build artifacts).

Features

  • Frozen: individual file marked immutable (cannot be modified/deleted)
  • Protected: folder marked immutable (contents cannot be changed)
  • Shielded: folder inherits immutable from protected parent
  • Container permissions: DeleteContainer, MoveContainer, SetImmutableFile, SetImmutableContainer
  • ACE encoding: D/m/i/I permission flags in POSIX ACEs
  • Descendant check: delete blocked when folder contains frozen/protected children
  • Performance: parent immutable state cached in ListFolder context

Changes

Area Files Change
Storage interface storage.go + 8 FS backends Add SetImmutable/GetImmutableState methods
decomposedfs decomposedfs.go, node.go Implement immutable logic + descendant check
Permissions permissions.go, role.go Container-specific permission checks
ACE ace.go Encode D/m/i/I flags
Grants grants.go Map container permissions to CS3 grants
Gateway storageprovider.go, labels.go Route SetImmutable/UnsetImmutable RPCs
PROPFIND propfind.go Return immutable state + strip permissions
Tests 4 test files Unit tests for permissions, node state, roles

Dependencies

Test plan

  • Set file as frozen → cannot modify/delete
  • Set folder as protected → contents shielded
  • Shielded items: D/NV/CK permissions stripped in PROPFIND
  • Delete folder with frozen child → blocked
  • Manager role has SetImmutable, Editor does not
  • Unit tests pass

flash and others added 12 commits June 21, 2026 16:03
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
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>
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