Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions model/sharing/files_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,37 @@ func TestFiles(t *testing.T) {
assert.False(t, IsValidDriveRootType("album"))
})

t.Run("CreateFileRootDriveShortcutMetadata", func(t *testing.T) {
s := Sharing{
SID: uuidv7(),
Drive: true,
DriveRootType: DriveRootTypeFile,
Rules: []Rule{
{
Title: "report.txt",
DocType: consts.Files,
Mime: "text/plain",
Values: []string{uuidv7()},
},
},
Members: []Member{
{Instance: "https://owner.example.net/"},
},
}

err := s.CreateDriveShortcut(inst, true)
require.NoError(t, err)
require.NotEmpty(t, s.ShortcutID)

shortcut, err := inst.VFS().FileByID(s.ShortcutID)
require.NoError(t, err)
target, ok := shortcut.Metadata["target"].(map[string]interface{})
require.True(t, ok)
assert.Equal(t, consts.Files, target["_type"])
assert.Equal(t, DriveRootTypeFile, target["drive_root_type"])
assert.Equal(t, "text/plain", target["mime"])
})

t.Run("SharingDir", func(t *testing.T) {
s := Sharing{
SID: uuidv7(),
Expand Down
6 changes: 5 additions & 1 deletion model/sharing/invitation.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,9 +341,13 @@ func (s *Sharing) CreateDriveShortcut(inst *instance.Instance, seen bool) error
"cozyMetadata": map[string]interface{}{
"instance": s.Members[0].Instance,
},
"_type": consts.Files,
"_type": consts.Files,
"drive_root_type": s.NormalizedDriveRootType(),
},
}
if rule := s.FirstFilesRule(); s.HasFileDriveRoot() && rule != nil && rule.Mime != "" {
fileDoc.Metadata["target"].(map[string]interface{})["mime"] = rule.Mime
}
fileDoc.AddReferencedBy(couchdb.DocReference{
ID: s.SID,
Type: consts.Sharings,
Expand Down
26 changes: 17 additions & 9 deletions model/sharing/sharing.go
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,9 @@ func (s *Sharing) Create(inst *instance.Instance) (*permission.Permission, error
if !IsValidDriveRootType(s.DriveRootType) {
return nil, ErrInvalidRule
}
s.inferDriveRootType(inst)
if err := s.inferDriveRootType(inst); err != nil {
return nil, err
}
}
if !s.Drive && len(s.Members) < 2 {
return nil, ErrNoRecipients
Expand All @@ -481,25 +483,28 @@ func (s *Sharing) Create(inst *instance.Instance) (*permission.Permission, error
return nil, nil
}

func (s *Sharing) inferDriveRootType(inst *instance.Instance) {
if s.DriveRootType != "" {
return
}
rootID, err := s.DriveRootID()
if err != nil {
return
func (s *Sharing) inferDriveRootType(inst *instance.Instance) error {
rule := s.FirstFilesRule()
if rule == nil || rule.Selector == couchdb.SelectorReferencedBy || len(rule.Values) == 0 {
return ErrDriveRootNotFound
}
rootID := rule.Values[0]
Comment on lines +487 to +491

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too bad we have to duplicate this code to get access to the rule :/.
I find rule.Values[0] not to be clear it represents the drive ID and was happy to see it used only in a helper.

But I don't have a better version to offer at the moment.


dir, file, err := inst.VFS().DirOrFileByID(rootID)
if err != nil {
return
return err
}
switch {
case file != nil:
s.DriveRootType = DriveRootTypeFile
rule.Mime = file.Mime
case dir != nil:
s.DriveRootType = DriveRootTypeDirectory
rule.Mime = ""
default:
return ErrDriveRootNotFound
}
return nil
}

// CreateDrive creates a new shared drive from an existing unshared folder or file.
Expand All @@ -514,9 +519,11 @@ func CreateDrive(inst *instance.Instance, rootID, description, appSlug string) (
if dir != nil {
rootName = dir.DocName
}
rootMime := ""
if file != nil {
rootType = DriveRootTypeFile
rootName = file.DocName
rootMime = file.Mime
}

s := &Sharing{
Expand All @@ -526,6 +533,7 @@ func CreateDrive(inst *instance.Instance, rootID, description, appSlug string) (
Rules: []Rule{{
Title: rootName,
DocType: consts.Files,
Mime: rootMime,
Values: []string{rootID},
Add: ActionRuleNone,
Update: ActionRuleNone,
Expand Down
130 changes: 129 additions & 1 deletion web/sharings/drives_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,15 @@ func createRootDirectory(t *testing.T, client *httpexpect.Expect, name, token st
// createFile creates a file with the given name in the specified parent directory
// and returns the file ID.
func createFile(t *testing.T, client *httpexpect.Expect, parentDirID, name, token string) string {
return createFileWithMime(t, client, parentDirID, name, token, "text/plain")
}

func createFileWithMime(t *testing.T, client *httpexpect.Expect, parentDirID, name, token, mime string) string {
t.Helper()
fileID := client.POST("/files/"+parentDirID).
WithQuery("Name", name).
WithQuery("Type", "file").
WithHeader("Content-Type", "text/plain").
WithHeader("Content-Type", mime).
WithHeader("Content-MD5", "rL0Y20zC+Fzt72VPzMSk2A==").
WithHeader("Authorization", "Bearer "+token).
WithBytes([]byte("foo")).
Expand Down Expand Up @@ -2418,6 +2422,7 @@ func TestSharedDriveCreation(t *testing.T) {
attrs.Value("description").String().IsEqual("Drive created from file")
rule := attrs.Value("rules").Array().Value(0).Object()
rule.Value("title").String().IsEqual("SharedDriveFile.txt")
rule.Value("mime").String().IsEqual("text/plain")
rule.Value("values").Array().Value(0).String().IsEqual(fileID)

sharedFile, err := env.acme.VFS().FileByID(fileID)
Expand Down Expand Up @@ -2524,6 +2529,129 @@ func TestSharedDriveCreation(t *testing.T) {
Object()

obj.Path("$.data.attributes.drive_root_type").String().IsEqual("file")
obj.Path("$.data.attributes.rules[0].mime").String().IsEqual("text/plain")
})

t.Run("LegacyDriveCreationKeepsExplicitFileRootTypeAndAddsMime", func(t *testing.T) {
eA, _, _ := env.createClients(t)
fileID := createFile(t, eA, "", "LegacyExplicitDriveFile.txt", env.acmeToken)

obj := eA.POST("/sharings/").
WithHeader("Authorization", "Bearer "+env.acmeToken).
WithHeader("Content-Type", "application/vnd.api+json").
WithBytes([]byte(fmt.Sprintf(`{
"data": {
"type": "%s",
"attributes": {
"description": "Legacy explicit file-root shared drive",
"drive": true,
"drive_root_type": "file",
"rules": [{
"title": "LegacyExplicitDriveFile.txt",
"doctype": "%s",
"values": ["%s"]
}]
}
}
}`, consts.Sharings, consts.Files, fileID))).
Expect().Status(201).
JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).
Object()

obj.Path("$.data.attributes.drive_root_type").String().IsEqual("file")
obj.Path("$.data.attributes.rules[0].mime").String().IsEqual("text/plain")
})

t.Run("LegacyDriveCreationOverridesClientFileRootMime", func(t *testing.T) {
eA, _, _ := env.createClients(t)
fileID := createFileWithMime(t, eA, "", "LegacyBinaryDriveFile.bin", env.acmeToken, "application/octet-stream")

obj := eA.POST("/sharings/").
WithHeader("Authorization", "Bearer "+env.acmeToken).
WithHeader("Content-Type", "application/vnd.api+json").
WithBytes([]byte(fmt.Sprintf(`{
"data": {
"type": "%s",
"attributes": {
"description": "Legacy binary file-root shared drive",
"drive": true,
"drive_root_type": "file",
"rules": [{
"title": "LegacyBinaryDriveFile.bin",
"doctype": "%s",
"mime": "text/plain",
"values": ["%s"]
}]
}
}
}`, consts.Sharings, consts.Files, fileID))).
Expect().Status(201).
JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).
Object()

obj.Path("$.data.attributes.drive_root_type").String().IsEqual("file")
obj.Path("$.data.attributes.rules[0].mime").String().IsEqual("application/octet-stream")
})

t.Run("LegacyDriveCreationClearsDirectoryRuleMime", func(t *testing.T) {
eA, _, _ := env.createClients(t)
dirID := createRootDirectory(t, eA, "LegacyDirectoryDriveWithMime", env.acmeToken)

obj := eA.POST("/sharings/").
WithHeader("Authorization", "Bearer "+env.acmeToken).
WithHeader("Content-Type", "application/vnd.api+json").
WithBytes([]byte(fmt.Sprintf(`{
"data": {
"type": "%s",
"attributes": {
"description": "Legacy directory-root shared drive",
"drive": true,
"drive_root_type": "directory",
"rules": [{
"title": "LegacyDirectoryDriveWithMime",
"doctype": "%s",
"mime": "evil/foo",
"values": ["%s"]
}]
}
}
}`, consts.Sharings, consts.Files, dirID))).
Expect().Status(201).
JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).
Object()

obj.Path("$.data.attributes.drive_root_type").String().IsEqual("directory")
obj.Path("$.data.attributes.rules[0]").Object().NotContainsKey("mime")
})

t.Run("LegacyDriveCreationCanonicalizesExplicitRootType", func(t *testing.T) {
eA, _, _ := env.createClients(t)
fileID := createFile(t, eA, "", "LegacyCanonicalDriveFile.txt", env.acmeToken)

obj := eA.POST("/sharings/").
WithHeader("Authorization", "Bearer "+env.acmeToken).
WithHeader("Content-Type", "application/vnd.api+json").
WithBytes([]byte(fmt.Sprintf(`{
"data": {
"type": "%s",
"attributes": {
"description": "Legacy canonical file-root shared drive",
"drive": true,
"drive_root_type": "directory",
"rules": [{
"title": "LegacyCanonicalDriveFile.txt",
"doctype": "%s",
"values": ["%s"]
}]
}
}
}`, consts.Sharings, consts.Files, fileID))).
Expect().Status(201).
JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}).
Object()

obj.Path("$.data.attributes.drive_root_type").String().IsEqual("file")
obj.Path("$.data.attributes.rules[0].mime").String().IsEqual("text/plain")
})
}

Expand Down
Loading