Skip to content

Add effective access resolver for additive nested shared folders #4837

Description

@shepilov

Context

Nested folder sharing is additive in v1:

  • parent folder access continues to apply to child folders
  • child folder shares can add users or grant higher rights
  • if a user has access through multiple sharing scopes, the highest permission wins
  • file shares remain additive and do not create boundaries
  • limited_access is reserved for a future feature

Before endpoints can safely allow nested folder shares, Cozy Stack needs shared backend helpers to compute effective access.

On the fly vs Copy Members

Option A: Copy inherited members into every child share

Pros:

  • permission checks stay simple
  • each child sharing doc contains a complete member list
  • less resolver logic needed initially

Cons:

  • parent membership changes require updating all nested child shares
  • removing a user from a parent share risks stale copied access in children
  • every inherited member must be replicated to every child share recipient
  • move/merge behavior becomes harder to reason about
  • child sharing docs become ambiguous: direct member, inherited member, copied member, or stale member

Option B: Access on the fly

Pros:

  • parent sharing remains the source of truth
  • no cascade updates when parent members change
  • revocation is immediate when parent access is removed
  • child sharing docs store only direct grants
  • works better with future limited_access

Cons:

  • permission checks are more complex
  • requires efficient CouchDB indexes
  • needs request-level caching/batching to avoid repeated lookups
  • archive/realtime/sync flows need extra care later

Decision

Use derived effective access.

Do not copy inherited parent members into child sharing docs during normal sharing updates.

Domain Model

Add/formalize access mode on io.cozy.sharings:

io.cozy.sharings

  • access_mode: additive | limited_access

Rules:

  • missing access_mode is treated as additive
  • additive is the only enabled mode for this task
  • limited_access is reserved for later
  • io.cozy.files.referenced_by remains the marker for shared roots
  • file-root shares are not restrictive boundaries

Internal service model:

 type SharingScope struct {
      SharingID  string
      RootID     string
      RootPath   string
      AccessMode string
      ReadOnly   bool
  }

  type EffectiveAccess struct {
      CanRead          bool
      CanWrite         bool
      SourceSharingIDs []string
  }

  type AccessResolver struct {
      inst *instance.Instance
      fs   vfs.VFS
  }
 
 func NewAccessResolver(inst *instance.Instance) *AccessResolver
 func (r *AccessResolver) Resolve(targetID string) (*EffectiveAccess, error)

access, err := resolver.Resolve(target vfs.Fetche)
  if err != nil {
      return err
  }
  if !access.Can(permission.GET) {
      return jsonapi.Forbidden(...)
  }

// Internal/private helpers can exist, but not as public API:

  func (r *AccessResolver) scopesFor(targetID string) ([]sharingScope, error)
  func (r *AccessResolver) ancestorPaths(targetID string) ([]string, error)
  func (r *AccessResolver) loadSharings(ids []string) ([]*Sharing, error)

Index

No index

We can have a Mango query instead of this index when we query all the files with the parent path using the same path index and then getinng sharing_ids from the reference_by list

req := &couchdb.FindRequest{
        UseIndex: "dir-by-path",
        Selector: mango.And(
                mango.In("path", ancestorPathsAsInterfaces),
                mango.Equal("type", consts.DirType),
                mango.Exists(couchdb.SelectorReferencedBy),
        ),
        Fields: []string{"_id", "path", "referenced_by"},
        Limit:  len(ancestorPaths),
  }

Flow:

  1. Read target path from the already loaded file/folder.
  2. Build ancestor folder paths.
  3. Query shared-folder-roots-by-path once with ViewRequest.Keys.
  4. Bulk-load returned sharing docs.
  5. Keep active additive sharing scopes.
  6. Merge current user permissions.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions