Skip to content

Commit

Permalink
Add a route to list shared drives
Browse files Browse the repository at this point in the history
  • Loading branch information
nono committed Feb 10, 2025
1 parent bb45146 commit 0e383a9
Show file tree
Hide file tree
Showing 11 changed files with 364 additions and 92 deletions.
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,5 +103,6 @@ designing new services.
- `/settings` - [Settings](settings.md)
- [Terms of Services](user-action-required.md)
- `/sharings` - [Sharing](sharing.md)
- [Shared drives](shared-drives.md)
- `/shortcuts` - [Shortcuts](shortcuts.md)
- `/.well-known` - [Well-known](wellknown.md)
91 changes: 91 additions & 0 deletions docs/shared-drives.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
[Table of contents](README.md#table-of-contents)

# Shared drives

A shared drive is a folder that is shared between several cozy instances. A
member doesn't have the files in their Cozy, but can access them via the stack
playing a proxy role.

To create a shared drive (typically on the organization Cozy), we need the
following steps:

1. Ensure that the `/Drive` folder exists in the cozy instance with the
[`POST /files/shared-drives`](https://docs.cozy.io/en/cozy-stack/files/#post-filesshared-drives)
route.
2. Create a folder inside it, with the name of shared drive.
3. Create a sharing with the `sharing: true` attribute, and one rule for
shared folder (with `none` for `add`, `update` and `remove` attributes).

## GET /sharings/drives

The `GET /sharings/drives` route returns the list of shared drives.

#### Request

```http
GET /sharings/drives HTTP/1.1
Host: acme.example.net
Accept: application/vnd.api+json
```

#### Response

```http
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
```

```json
{
"data": [
{
"type": "io.cozy.sharings",
"id": "aae62886e79611ef8381fb83ff72e425",
"attributes": {
"drive": true,
"owner": true,
"description": "Drive for the product team",
"app_slug": "drive",
"created_at": "2025-02-10T11:08:08Z",
"updated_at": "2025-02-10T12:10:43Z",
"members": [
{
"status": "owner",
"public_name": "ACME",
"email": "[email protected]",
"instance": "acme.example.net"
},
{
"status": "pending",
"name": "Alice",
"email": "[email protected]"
},
{
"status": "pending",
"name": "Bob",
"email": "[email protected]"
}
],
"rules": [
{
"title": "Product team",
"doctype": "io.cozy.files",
"values": [
"357665ec-e797-11ef-94fb-f3d08ccb3ff5"
],
"add": "none",
"update": "none",
"remove": "none"
}
]
},
"meta": {
"rev": "1-272ba74b868f"
},
"links": {
"self": "/sharings/aae62886e79611ef8381fb83ff72e425"
}
}
]
}
```
13 changes: 10 additions & 3 deletions docs/sharing-design.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
[Table of contents](README.md#table-of-contents)

# Sharing design
# Synchronized sharing design

## Baseline

Here we detail the baseline of the Cozy sharing design and provide some core
statements.
Here we detail the baseline of the Cozy sharing design where files are
synchronized between several cozy instances.

**Note:** shared drives is a newer feature, and it does not require to
synchronize the files between the cozy instances. It is still possible to share
files between cozy instances. As such, it is more scalable but does not provide
the same features as the synchronized sharing. In particular, when a shared drive
is revoked, the member has no longer access to a copy of the files in it.

### Data sync

Expand Down Expand Up @@ -512,6 +518,7 @@ care of it later.
- `true` if any member of the sharing except the read-only ones can add a
new recipient
- `false` if only the owner can add a new recipient
- A flag `drive`, that is false for a synchronized sharing
- Some technical data (`created_at`, `updated_at`, `app_slug`, `preview_path`,
`triggers`, `credentials`)
- A flag `initial_sync` present only when the initial replication is still
Expand Down
107 changes: 54 additions & 53 deletions docs/toc.yml
Original file line number Diff line number Diff line change
@@ -1,58 +1,59 @@
- README: ./README.md
- Usage:
- "Install the cozy-stack": ./INSTALL.md
- "Configuration file": ./config.md
- "Managing Instances": ./instance.md
- "Security": ./security.md
- "Manpages of the command-line tool": ./cli/cozy-stack.md
- "Using the admin API": "./admin.md"
- "Important changes": "./important-changes.md"
- "Install the cozy-stack": ./INSTALL.md
- "Configuration file": ./config.md
- "Managing Instances": ./instance.md
- "Security": ./security.md
- "Manpages of the command-line tool": ./cli/cozy-stack.md
- "Using the admin API": "./admin.md"
- "Important changes": "./important-changes.md"
- How-to guides for developpers:
- "Using the HTTP API": ./http-api.md
- "Develop a client-side app": ./client-app-dev.md
- "Running and building Docker images": ./docker.md
- "Running a konnector locally": ./konnectors-dev.md
- "Adding a new doctype": ./doctype.md
- "Working with the stack assets": ./assets.md
- "Build a release": ./release.md
- "The contributing guide": ./CONTRIBUTING.md
- "Using the HTTP API": ./http-api.md
- "Develop a client-side app": ./client-app-dev.md
- "Running and building Docker images": ./docker.md
- "Running a konnector locally": ./konnectors-dev.md
- "Adding a new doctype": ./doctype.md
- "Working with the stack assets": ./assets.md
- "Build a release": ./release.md
- "The contributing guide": ./CONTRIBUTING.md
- Explanations:
- "Flagship app": ./flagship.md
- "Move design": ./move-design.md
- "Realtime internals": ./realtime-internals.md
- "Sharing design": ./sharing-design.md
- "Workflow of the konnectors": ./konnectors-workflow.md
- "Flagship app": ./flagship.md
- "Move design": ./move-design.md
- "Realtime internals": ./realtime-internals.md
- "Sharing design": ./sharing-design.md
- "Workflow of the konnectors": ./konnectors-workflow.md
- List of services:
- "/ai - AI": ./ai.md
- "/auth - Authentication & OAuth": ./auth.md
- " /oidc - Delegated authentication": ./delegated-auth.md
- "/apps - Applications Management": ./apps.md
- " /apps - Apps registry": ./registry.md
- "/bitwarden - Bitwarden": ./bitwarden.md
- "/connection_check - Connection check": ./connection-check.md
- "/contacts - Contacts": ./contacts.md
- "/data - Data System": ./data-system.md
- " /data - Mango": ./mango.md
- " /data - CouchDB Quirks": ./couchdb-quirks.md
- " /data - PouchDB Quirks": ./pouchdb-quirks.md
- "/files - Virtual File System": ./files.md
- " /files - Not synchronized directories": ./not-synchronized-vfs.md
- " /files - References of documents in VFS": ./references-docs-in-vfs.md
- "/intents - Intents": ./intents.md
- "/jobs - Jobs": ./jobs.md
- " /jobs - Workers": ./workers.md
- "/konnectors - Konnectors": ./konnectors.md
- "/move - Move, export and import an instance": ./move.md
- "/notes - Notes for collaborative edition": ./notes.md
- "/notifications - Notifications": ./notifications.md
- "/office - Collaborative edition of Office documents": ./office.md
- "/public - Public": ./public.md
- "/permissions - Permissions": ./permissions.md
- "/realtime - Realtime": ./realtime.md
- "/remote - Proxy for remote data/API": ./remote.md
- " /remote/nextcloud - NextCloud": ./nextcloud.md
- "/settings - Settings": ./settings.md
- " /settings - Terms of Services": ./user-action-required.md
- "/sharings - Sharing": ./sharing.md
- "/shortcuts - Shortcuts": ./shortcuts.md
- "/.well-known - Well-known": ./wellknown.md
- "/ai - AI": ./ai.md
- "/auth - Authentication & OAuth": ./auth.md
- " /oidc - Delegated authentication": ./delegated-auth.md
- "/apps - Applications Management": ./apps.md
- " /apps - Apps registry": ./registry.md
- "/bitwarden - Bitwarden": ./bitwarden.md
- "/connection_check - Connection check": ./connection-check.md
- "/contacts - Contacts": ./contacts.md
- "/data - Data System": ./data-system.md
- " /data - Mango": ./mango.md
- " /data - CouchDB Quirks": ./couchdb-quirks.md
- " /data - PouchDB Quirks": ./pouchdb-quirks.md
- "/files - Virtual File System": ./files.md
- " /files - Not synchronized directories": ./not-synchronized-vfs.md
- " /files - References of documents in VFS": ./references-docs-in-vfs.md
- "/intents - Intents": ./intents.md
- "/jobs - Jobs": ./jobs.md
- " /jobs - Workers": ./workers.md
- "/konnectors - Konnectors": ./konnectors.md
- "/move - Move, export and import an instance": ./move.md
- "/notes - Notes for collaborative edition": ./notes.md
- "/notifications - Notifications": ./notifications.md
- "/office - Collaborative edition of Office documents": ./office.md
- "/public - Public": ./public.md
- "/permissions - Permissions": ./permissions.md
- "/realtime - Realtime": ./realtime.md
- "/remote - Proxy for remote data/API": ./remote.md
- " /remote/nextcloud - NextCloud": ./nextcloud.md
- "/settings - Settings": ./settings.md
- " /settings - Terms of Services": ./user-action-required.md
- "/sharings - Sharing": ./sharing.md
- " /sharings/drives - Shared drives": ./shared-drives.md
- "/shortcuts - Shortcuts": ./shortcuts.md
- "/.well-known - Well-known": ./wellknown.md
1 change: 1 addition & 0 deletions model/sharing/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func (m *Member) CreateSharingRequest(inst *instance.Instance, s *Sharing, c *Cr
sh := APISharing{
&Sharing{
SID: s.SID,
Drive: s.Drive,
Active: false,
Owner: false,
Open: s.Open,
Expand Down
73 changes: 39 additions & 34 deletions model/sharing/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import (
// SetupReceiver is used on the receivers' cozy to make sure the cozy can
// receive the shared documents.
func (s *Sharing) SetupReceiver(inst *instance.Instance) error {
if s.Drive {
return nil
}
inst.Logger().WithNamespace("sharing").
Debugf("Setup receiver on %#v", inst)

Expand Down Expand Up @@ -93,50 +96,52 @@ func (s *Sharing) Setup(inst *instance.Instance, m *Member) {
}
defer mu.Unlock()

if err := couchdb.EnsureDBExist(inst, consts.Shared); err != nil {
inst.Logger().WithNamespace("sharing").
Warnf("Can't ensure io.cozy.shared exists (%s): %s", s.SID, err)
}
if rule := s.FirstFilesRule(); rule != nil && rule.Selector != couchdb.SelectorReferencedBy {
if err := s.AddReferenceForSharingDir(inst, rule); err != nil {
if !s.Drive {
if err := couchdb.EnsureDBExist(inst, consts.Shared); err != nil {
inst.Logger().WithNamespace("sharing").
Warnf("Error on referenced_by for the sharing dir (%s): %s", s.SID, err)
Warnf("Can't ensure io.cozy.shared exists (%s): %s", s.SID, err)
}
if rule := s.FirstFilesRule(); rule != nil && rule.Selector != couchdb.SelectorReferencedBy {
if err := s.AddReferenceForSharingDir(inst, rule); err != nil {
inst.Logger().WithNamespace("sharing").
Warnf("Error on referenced_by for the sharing dir (%s): %s", s.SID, err)
}
}
}

if err := s.AddTrackTriggers(inst); err != nil {
inst.Logger().WithNamespace("sharing").
Warnf("Error on setup of track triggers (%s): %s", s.SID, err)
}
if s.Triggers.ReplicateID == "" {
for i, rule := range s.Rules {
if err := s.InitialIndex(inst, rule, i); err != nil {
inst.Logger().Warnf("Error on initial copy for %s (%s): %s", rule.Title, s.SID, err)
if err := s.AddTrackTriggers(inst); err != nil {
inst.Logger().WithNamespace("sharing").
Warnf("Error on setup of track triggers (%s): %s", s.SID, err)
}
if s.Triggers.ReplicateID == "" {
for i, rule := range s.Rules {
if err := s.InitialIndex(inst, rule, i); err != nil {
inst.Logger().Warnf("Error on initial copy for %s (%s): %s", rule.Title, s.SID, err)
}
}
}
}
if err := s.AddReplicateTrigger(inst); err != nil {
inst.Logger().WithNamespace("sharing").
Warnf("Error on setup replicate trigger (%s): %s", s.SID, err)
}
if s.FirstFilesRule() != nil {
if err := s.AddUploadTrigger(inst); err != nil {
if err := s.AddReplicateTrigger(inst); err != nil {
inst.Logger().WithNamespace("sharing").
Warnf("Error on setup upload trigger (%s): %s", s.SID, err)
Warnf("Error on setup replicate trigger (%s): %s", s.SID, err)
}
}
if err := s.InitialReplication(inst, m); err != nil {
inst.Logger().WithNamespace("sharing").
Warnf("Error on initial replication (%s): %s", s.SID, err)
s.retryWorker(inst, "share-replicate", 0)
if s.FirstFilesRule() != nil {
s.retryWorker(inst, "share-upload", 1) // 1, so that it will start after share-replicate
if err := s.AddUploadTrigger(inst); err != nil {
inst.Logger().WithNamespace("sharing").
Warnf("Error on setup upload trigger (%s): %s", s.SID, err)
}
}
} else if s.FirstFilesRule() != nil {
if err := s.InitialUpload(inst, m); err != nil {
if err := s.InitialReplication(inst, m); err != nil {
inst.Logger().WithNamespace("sharing").
Warnf("Error on initial upload (%s): %s", s.SID, err)
s.retryWorker(inst, "share-upload", 0)
Warnf("Error on initial replication (%s): %s", s.SID, err)
s.retryWorker(inst, "share-replicate", 0)
if s.FirstFilesRule() != nil {
s.retryWorker(inst, "share-upload", 1) // 1, so that it will start after share-replicate
}
} else if s.FirstFilesRule() != nil {
if err := s.InitialUpload(inst, m); err != nil {
inst.Logger().WithNamespace("sharing").
Warnf("Error on initial upload (%s): %s", s.SID, err)
s.retryWorker(inst, "share-upload", 0)
}
}
}

Expand Down
21 changes: 20 additions & 1 deletion model/sharing/sharing.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type Sharing struct {
SRev string `json:"_rev,omitempty"`

Triggers Triggers `json:"triggers"`
Drive bool `json:"drive,omitempty"`
Active bool `json:"active,omitempty"`
Owner bool `json:"owner,omitempty"`
Open bool `json:"open_sharing,omitempty"`
Expand Down Expand Up @@ -157,7 +158,7 @@ func (s *Sharing) BeOwner(inst *instance.Instance, slug string) error {
if s.AppSlug == "" {
s.AppSlug = slug
}
if s.AppSlug == "" {
if s.Drive || s.AppSlug == "" {
s.PreviewPath = ""
}
s.CreatedAt = time.Now()
Expand Down Expand Up @@ -735,6 +736,24 @@ func FindActive(db prefixer.Prefixer) ([]*Sharing, error) {
return res, nil
}

// ListDrives returns the list of the active sharings with drive: true.
func ListDrives(db prefixer.Prefixer) ([]*Sharing, error) {
req := &couchdb.FindRequest{
UseIndex: "active",
Selector: mango.And(
mango.Equal("active", true),
mango.Equal("drive", true),
),
Limit: 1000,
}
var res []*Sharing
err := couchdb.FindDocs(db, consts.Sharings, req, &res)
if err != nil {
return nil, err
}
return res, nil
}

// GetSharingsByDocType returns all the sharings for the given doctype
func GetSharingsByDocType(inst *instance.Instance, docType string) (map[string]*Sharing, error) {
req := &couchdb.ViewRequest{
Expand Down
2 changes: 1 addition & 1 deletion web/auth/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,7 @@ func (a *AuthorizeHTTPHandler) authorizeSharingForm(c echo.Context) error {
return renderError(c, http.StatusUnauthorized, "Error Invalid sharing")
}

if strings.ToLower(c.QueryParam("shortcut")) == "true" {
if s.Drive || strings.ToLower(c.QueryParam("shortcut")) == "true" {
if err := s.AddShortcut(instance, params.state); err != nil {
return err
}
Expand Down
Loading

0 comments on commit 0e383a9

Please sign in to comment.