diff --git a/changelog/unreleased/add-remote-item-id-to-webdav-report.md b/changelog/unreleased/add-remote-item-id-to-webdav-report.md new file mode 100644 index 0000000000..1542f7ce2c --- /dev/null +++ b/changelog/unreleased/add-remote-item-id-to-webdav-report.md @@ -0,0 +1,6 @@ +Enhancement: Add remote item id to WebDAV report responses + +The remote item id has been added to WebDAV `REPORT` responses. + +https://github.com/owncloud/ocis/issues/9094 +https://github.com/owncloud/ocis/pull/9095 diff --git a/docs/apis/http/webdav/_index.md b/docs/apis/http/webdav/_index.md index b947d3e939..01371ff558 100644 --- a/docs/apis/http/webdav/_index.md +++ b/docs/apis/http/webdav/_index.md @@ -323,36 +323,49 @@ Available namespaces: ### Supported WebDAV Properties -| Property | Desription | Example | -|-----------------------------|----------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------| -| `` | The latest modification time. | `Fri, 30 Dec 2022 14:22:43 GMT` | -| `` | The file's etag. | `"c3a1ee4a0c28edc15b9635c3bf798013"` | -| `` | The mime type of the file. | `image/jpeg` | -| `` | Specifies the nature of the resource. | `` for a folder | -| `` | The size if it is a file in bytes. | `5` bytes | -| `` | Describes the active locks on a resource. | Detailed Example in [Locking]() | -| `` | The globally unique ID of the resource. | `storage-1$27475553-7fb7-4689-b4cf-bbb635daff79!27475553-7fb7-4689-b4cf-bbb635daff79` | -| `` | The globally unique ID of the resource. | `storage-1$27475553-7fb7-4689-b4cf-bbb635daff79!27475553-7fb7-4689-b4cf-bbb635daff79` | -| `` | Direct URL to download a file from. | Not implemented. | -| `` | The permissions a user has on the resource. | The value is a string containing letters for all available permissions. | -| | | `S`: Shared | -| | | `M`: Mounted | -| | | `D`: Deletable | -| | | `NV`: Updateable, Renameable, Moveable | -| | | `W`: Updateable (file) | -| | | `CK`: Creatable (folders only) | -| | | `Z`: Deniable | -| | | `P`: Trashbin Purgable | -| `` | List of user specified tags. | `test` | -| ` ` | The favorite state. | `0` for not favourited, `1` for favourited | -| `` | The user id of the owner of a resource. Project spaces have no owner. | `einstein` | -| `` | The display name of the owner of a resource. Project spaces have no owner. | `Albert Einstein` | -| `` | List of share types. | `0` = User Share | -| | | `1` = Group Share | -| | | `2` = Public Link | -| `` | | ``
`SHA1:1c68ea370b40c06fcaf7f26c8b1dba9d9caf5dea MD5:2205e48de5f93c784733ffcca841d2b5 ADLER32:058801ab`
`
` | -| | | Due to a bug in the very early development of ownCloud, this value is not an array, but a string separated by whitespaces. | -| `` | Similar to `getcontentlength` but it also works for folders. | `10` bytes | +| Property | Desription | Example | +| ----------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| `` | The latest modification time. | `Fri, 30 Dec 2022 14:22:43 GMT` | +| `` | The file's etag. | `"c3a1ee4a0c28edc15b9635c3bf798013"` | +| `` | The mime type of the file. | `image/jpeg` | +| `` | Specifies the nature of the resource. | `` for a folder | +| `` | The size if it is a file in bytes. | `5` bytes | +| `` | Describes the active locks on a resource. | Detailed Example in [Locking]() | +| `` | The globally unique ID of the resource. | `storage-1$27475553-7fb7-4689-b4cf-bbb635daff79!27475553-7fb7-4689-b4cf-bbb635daff79` | +| `` | The globally unique ID of the resource. | `storage-1$27475553-7fb7-4689-b4cf-bbb635daff79!27475553-7fb7-4689-b4cf-bbb635daff79` | +| `` | Direct URL to download a file from. | Not implemented. | +| `` | The permissions a user has on the resource. | The value is a string containing letters for all available permissions. | +| | | `S`: Shared | +| | | `M`: Mounted | +| | | `D`: Deletable | +| | | `NV`: Updateable, Renameable, Moveable | +| | | `W`: Updateable (file) | +| | | `CK`: Creatable (folders only) | +| | | `Z`: Deniable | +| | | `P`: Trashbin Purgable | +| `` | List of user specified tags. | `test` | +| ` ` | The favorite state. | `0` for not favourited, `1` for favourited | +| `` | The user id of the owner of a resource. Project spaces have no owner. | `einstein` | +| `` | The display name of the owner of a resource. Project spaces have no owner. | `Albert Einstein` | +| `` | List of share types. | `0` = User Share | +| | | `1` = Group Share | +| | | `2` = Public Link | +| `` | | ``
`SHA1:1c68ea370b40c06fcaf7f26c8b1dba9d9caf5dea MD5:2205e48de5f93c784733ffcca841d2b5 ADLER32:058801ab`
`
` | +| | | Due to a bug in the very early development of ownCloud, this value is not an array, but a string separated by whitespaces. | +| `` | Similar to `getcontentlength` but it also works for folders. | `10` bytes | +| `` | The ID of the share if the resource is part of such. | `storage-1$27475553-7fb7-4689-b4cf-bbb635daff79!27475553-7fb7-4689-b4cf-bbb635daff79` | +| `` | The root path of the shared resource if the resource is part of such. | `/shared-folder` | +| `` | The ID of the shared resource if the resource is part of such. | `storage-1$27475553-7fb7-4689-b4cf-bbb635daff79!27475553-7fb7-4689-b4cf-bbb635daff79` | +| `` | The type of the resource if it's a public link. | `folder` | +| `` | The share permissions of the resource if it's a public link. | `1` | +| `` | The expiration date of the public link. | `Tue, 14 May 2024 12:44:29 GMT` | +| `` | The date the public link was created. | `Tue, 14 May 2024 12:44:29 GMT` | +| `` | The username of the user who created the public link. | `admin` | +| `` | The original name of the resource before it was deleted. | `some-file.txt` | +| `` | The original location of the resource before it was deleted. | `some-file.txt` | +| `` | The date the resource was deleted. | `Tue, 14 May 2024 12:44:29 GMT` | +| `` | Audio meta data if the resource contains such. | `MetallicaMetallicaEnter Sandman` | +| `` | Location meta data if the resource contains such. | `51.504106-0.074575` | ### Request Headers diff --git a/protogen/gen/ocis/messages/search/v0/search.pb.go b/protogen/gen/ocis/messages/search/v0/search.pb.go index 0349d3e177..0cca4f065a 100644 --- a/protogen/gen/ocis/messages/search/v0/search.pb.go +++ b/protogen/gen/ocis/messages/search/v0/search.pb.go @@ -390,6 +390,7 @@ type Entity struct { Highlights string `protobuf:"bytes,14,opt,name=highlights,proto3" json:"highlights,omitempty"` Audio *Audio `protobuf:"bytes,15,opt,name=audio,proto3" json:"audio,omitempty"` Location *GeoCoordinates `protobuf:"bytes,16,opt,name=location,proto3" json:"location,omitempty"` + RemoteItemId *ResourceID `protobuf:"bytes,17,opt,name=remote_item_id,json=remoteItemId,proto3" json:"remote_item_id,omitempty"` } func (x *Entity) Reset() { @@ -536,6 +537,13 @@ func (x *Entity) GetLocation() *GeoCoordinates { return nil } +func (x *Entity) GetRemoteItemId() *ResourceID { + if x != nil { + return x.RemoteItemId + } + return nil +} + type Match struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -669,8 +677,8 @@ var file_ocis_messages_search_v0_search_proto_rawDesc = []byte{ 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x48, 0x02, 0x52, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x61, 0x6c, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, - 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x22, 0xfd, - 0x04, 0x0a, 0x06, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x34, 0x0a, 0x03, 0x72, 0x65, 0x66, + 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x22, 0xc8, + 0x05, 0x0a, 0x06, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x34, 0x0a, 0x03, 0x72, 0x65, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12, @@ -709,18 +717,22 @@ var file_ocis_messages_search_v0_search_proto_rawDesc = []byte{ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x47, 0x65, 0x6f, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, - 0x61, 0x74, 0x65, 0x73, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x56, - 0x0a, 0x05, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x37, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, - 0x30, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x52, - 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x42, 0x42, 0x5a, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, - 0x69, 0x73, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67, - 0x65, 0x6e, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, - 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2f, 0x76, 0x30, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x61, 0x74, 0x65, 0x73, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x49, + 0x0a, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x69, 0x64, + 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, + 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x44, 0x52, 0x0c, 0x72, 0x65, 0x6d, + 0x6f, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x22, 0x56, 0x0a, 0x05, 0x4d, 0x61, 0x74, + 0x63, 0x68, 0x12, 0x37, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, + 0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x72, + 0x65, 0x42, 0x42, 0x5a, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x76, 0x32, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x6f, 0x63, + 0x69, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2f, 0x73, 0x65, 0x61, 0x72, + 0x63, 0x68, 0x2f, 0x76, 0x30, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -753,12 +765,13 @@ var file_ocis_messages_search_v0_search_proto_depIdxs = []int32{ 0, // 4: ocis.messages.search.v0.Entity.parent_id:type_name -> ocis.messages.search.v0.ResourceID 2, // 5: ocis.messages.search.v0.Entity.audio:type_name -> ocis.messages.search.v0.Audio 3, // 6: ocis.messages.search.v0.Entity.location:type_name -> ocis.messages.search.v0.GeoCoordinates - 4, // 7: ocis.messages.search.v0.Match.entity:type_name -> ocis.messages.search.v0.Entity - 8, // [8:8] is the sub-list for method output_type - 8, // [8:8] is the sub-list for method input_type - 8, // [8:8] is the sub-list for extension type_name - 8, // [8:8] is the sub-list for extension extendee - 0, // [0:8] is the sub-list for field type_name + 0, // 7: ocis.messages.search.v0.Entity.remote_item_id:type_name -> ocis.messages.search.v0.ResourceID + 4, // 8: ocis.messages.search.v0.Match.entity:type_name -> ocis.messages.search.v0.Entity + 9, // [9:9] is the sub-list for method output_type + 9, // [9:9] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name } func init() { file_ocis_messages_search_v0_search_proto_init() } diff --git a/protogen/gen/ocis/services/search/v0/search.swagger.json b/protogen/gen/ocis/services/search/v0/search.swagger.json index d84ac28f33..98a0c0e356 100644 --- a/protogen/gen/ocis/services/search/v0/search.swagger.json +++ b/protogen/gen/ocis/services/search/v0/search.swagger.json @@ -273,6 +273,9 @@ }, "location": { "$ref": "#/definitions/v0GeoCoordinates" + }, + "remoteItemId": { + "$ref": "#/definitions/v0ResourceID" } } }, diff --git a/protogen/proto/ocis/messages/search/v0/search.proto b/protogen/proto/ocis/messages/search/v0/search.proto index 84b2b9c250..eab871217d 100644 --- a/protogen/proto/ocis/messages/search/v0/search.proto +++ b/protogen/proto/ocis/messages/search/v0/search.proto @@ -59,6 +59,7 @@ message Entity { string highlights = 14; Audio audio = 15; GeoCoordinates location = 16; + ResourceID remote_item_id = 17; } message Match { diff --git a/services/search/pkg/search/service.go b/services/search/pkg/search/service.go index 40184a741a..2a473d8cbf 100644 --- a/services/search/pkg/search/service.go +++ b/services/search/pkg/search/service.go @@ -278,6 +278,7 @@ func (s *Service) searchIndex(ctx context.Context, req *searchsvc.SearchRequest, mountpointRootID *searchmsg.ResourceID rootName string permissions *provider.ResourcePermissions + remoteItemId *searchmsg.ResourceID ) mountpointPrefix := "" searchPathPrefix := req.Ref.GetPath() @@ -329,6 +330,11 @@ func (s *Service) searchIndex(ctx context.Context, req *searchsvc.SearchRequest, } rootName = space.GetRootInfo().GetPath() permissions = space.GetRootInfo().GetPermissionSet() + remoteItemId = &searchmsg.ResourceID{ + StorageId: space.GetRootInfo().GetId().GetStorageId(), + SpaceId: space.GetRootInfo().GetId().GetSpaceId(), + OpaqueId: space.GetRootInfo().GetId().GetOpaqueId(), + } s.logger.Debug().Interface("grantSpace", space).Interface("mountpointRootId", mountpointRootID).Msg("searching a grant") case _spaceTypePersonal, _spaceTypeProject: permissions = space.GetRootInfo().GetPermissionSet() @@ -365,6 +371,7 @@ func (s *Service) searchIndex(ctx context.Context, req *searchsvc.SearchRequest, match.Entity.Ref.ResourceId = mountpointRootID } match.Entity.ShareRootName = rootName + match.Entity.RemoteItemId = remoteItemId isShared := match.GetEntity().GetRef().GetResourceId().GetSpaceId() == utils.ShareStorageSpaceID isMountpoint := isShared && match.GetEntity().GetRef().GetPath() == "." diff --git a/services/search/pkg/search/service_test.go b/services/search/pkg/search/service_test.go index d54b8d5adb..57a89b3ecc 100644 --- a/services/search/pkg/search/service_test.go +++ b/services/search/pkg/search/service_test.go @@ -281,6 +281,13 @@ var _ = Describe("Searchprovider", func() { Id: &sprovider.StorageSpaceId{OpaqueId: "storageproviderid$spaceid!otherspacegrant"}, Root: &sprovider.ResourceId{StorageId: "storageproviderid", SpaceId: "spaceid", OpaqueId: "otherspacegrant"}, Name: "grantspace", + RootInfo: &sprovider.ResourceInfo{ + Id: &sprovider.ResourceId{ + StorageId: "storageid", + SpaceId: "spaceid", + OpaqueId: "opaqueid", + }, + }, } mountpointSpace = &sprovider.StorageSpace{ SpaceType: "mountpoint", @@ -345,6 +352,11 @@ var _ = Describe("Searchprovider", func() { Expect(match.Entity.Name).To(Equal("Shared.pdf")) Expect(match.Entity.Ref.ResourceId.OpaqueId).To(Equal(mountpointSpace.Root.OpaqueId)) Expect(match.Entity.Ref.Path).To(Equal("./to/Shared.pdf")) + Expect(match.Entity.RemoteItemId).To(Equal(&searchmsg.ResourceID{ + StorageId: grantSpace.RootInfo.Id.StorageId, + SpaceId: grantSpace.RootInfo.Id.SpaceId, + OpaqueId: grantSpace.RootInfo.Id.OpaqueId, + })) }) Context("when searching both spaces", func() { diff --git a/services/webdav/pkg/service/v0/search.go b/services/webdav/pkg/service/v0/search.go index 94c2efbb5c..a5818f88db 100644 --- a/services/webdav/pkg/service/v0/search.go +++ b/services/webdav/pkg/service/v0/search.go @@ -185,6 +185,11 @@ func matchToPropResponse(ctx context.Context, match *searchmsg.Match) (*propfind if match.Entity.Ref.ResourceId.StorageId == utils.ShareStorageProviderID { propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:shareid", match.Entity.Ref.ResourceId.OpaqueId)) propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:shareroot", match.Entity.ShareRootName)) + propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:remote-item-id", storagespace.FormatResourceID(provider.ResourceId{ + StorageId: match.Entity.GetRemoteItemId().GetStorageId(), + SpaceId: match.Entity.GetRemoteItemId().GetSpaceId(), + OpaqueId: match.Entity.GetRemoteItemId().GetOpaqueId(), + }))) } propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("oc:name", match.Entity.Name)) propstatOK.Prop = append(propstatOK.Prop, prop.Escaped("d:getlastmodified", match.Entity.LastModifiedTime.AsTime().Format(time.RFC3339)))