diff --git a/changelog/unreleased/fix-space-shares.md b/changelog/unreleased/fix-space-shares.md new file mode 100644 index 0000000000..894325a689 --- /dev/null +++ b/changelog/unreleased/fix-space-shares.md @@ -0,0 +1,5 @@ +Bugfix: Fix the resource id handling for space shares + +Adapt the space shares to the new id format. + +https://github.com/cs3org/reva/pull/2802 diff --git a/changelog/unreleased/ocs-concurrent-stat.md b/changelog/unreleased/ocs-concurrent-stat.md deleted file mode 100644 index 962c722d29..0000000000 --- a/changelog/unreleased/ocs-concurrent-stat.md +++ /dev/null @@ -1,3 +0,0 @@ -Enhancement: Concurrently resolve shares in ocs HTTP service - -https://github.com/cs3org/reva/pull/2787 \ No newline at end of file diff --git a/internal/grpc/services/gateway/usershareprovider.go b/internal/grpc/services/gateway/usershareprovider.go index edb577f915..85e1d2519d 100644 --- a/internal/grpc/services/gateway/usershareprovider.go +++ b/internal/grpc/services/gateway/usershareprovider.go @@ -35,6 +35,7 @@ import ( "github.com/cs3org/reva/v2/pkg/share" "github.com/cs3org/reva/v2/pkg/storage/utils/grants" rtrace "github.com/cs3org/reva/v2/pkg/trace" + "github.com/cs3org/reva/v2/pkg/utils/resourceid" "github.com/pkg/errors" ) @@ -778,10 +779,8 @@ func refIsSpaceRoot(ref *provider.ResourceId) bool { if ref.StorageId == "" || ref.OpaqueId == "" { return false } - if ref.StorageId != ref.OpaqueId { - return false - } - return true + sid, _ := resourceid.StorageIDUnwrap(ref.GetStorageId()) + return sid == ref.OpaqueId } func shareIsSpaceRoot(key *collaboration.ShareKey) bool { diff --git a/internal/grpc/services/storageprovider/storageprovider.go b/internal/grpc/services/storageprovider/storageprovider.go index 1e3ccee676..245d916e6e 100644 --- a/internal/grpc/services/storageprovider/storageprovider.go +++ b/internal/grpc/services/storageprovider/storageprovider.go @@ -506,6 +506,8 @@ func (s *service) CreateStorageSpace(ctx context.Context, req *provider.CreateSt }, nil } + resp.StorageSpace.Id.OpaqueId = resourceid.StorageIDWrap(resp.StorageSpace.Id.GetOpaqueId(), s.conf.MountID) + resp.StorageSpace.Root.StorageId = resourceid.StorageIDWrap(resp.StorageSpace.Root.GetStorageId(), s.conf.MountID) return resp, nil } @@ -582,6 +584,7 @@ func (s *service) UpdateStorageSpace(ctx context.Context, req *provider.UpdateSt return nil, err } res.StorageSpace.Id.OpaqueId = resourceid.StorageIDWrap(res.StorageSpace.Id.GetOpaqueId(), s.conf.MountID) + res.StorageSpace.Root.StorageId = resourceid.StorageIDWrap(res.StorageSpace.Root.GetStorageId(), s.conf.MountID) return res, nil } diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go index 39e34bc7b1..a56444daaa 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/public.go @@ -19,14 +19,11 @@ package shares import ( - "context" "encoding/json" "fmt" "net/http" "strconv" - "sync" - gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" @@ -183,68 +180,52 @@ func (h *Handler) listPublicShares(r *http.Request, filters []*link.ListPublicSh log := appctx.GetLogger(ctx) ocsDataPayload := make([]*conversions.ShareData, 0) - client, err := pool.GetGatewayServiceClient(h.gatewayAddr) - if err != nil { - return ocsDataPayload, nil, err - } - - req := link.ListPublicSharesRequest{ - Filters: filters, - } + // TODO(refs) why is this guard needed? Are we moving towards a gateway only for service discovery? without a gateway this is dead code. + if h.gatewayAddr != "" { + client, err := pool.GetGatewayServiceClient(h.gatewayAddr) + if err != nil { + return ocsDataPayload, nil, err + } - res, err := client.ListPublicShares(ctx, &req) - if err != nil { - return ocsDataPayload, nil, err - } - if res.Status.Code != rpc.Code_CODE_OK { - return ocsDataPayload, res.Status, nil - } + req := link.ListPublicSharesRequest{ + Filters: filters, + } - var wg sync.WaitGroup - workers := 50 - input := make(chan *link.PublicShare, len(res.Share)) - output := make(chan *conversions.ShareData, len(res.Share)) + res, err := client.ListPublicShares(ctx, &req) + if err != nil { + return ocsDataPayload, nil, err + } + if res.Status.Code != rpc.Code_CODE_OK { + return ocsDataPayload, res.Status, nil + } - for i := 0; i < workers; i++ { - wg.Add(1) - go func(ctx context.Context, client gateway.GatewayAPIClient, input chan *link.PublicShare, output chan *conversions.ShareData, wg *sync.WaitGroup) { - defer wg.Done() + for _, share := range res.GetShare() { + info, status, err := h.getResourceInfoByID(ctx, client, share.ResourceId) + if err != nil || status.Code != rpc.Code_CODE_OK { + log.Debug().Interface("share", share).Interface("status", status).Err(err).Msg("could not stat share, skipping") + continue + } - for share := range input { - info, status, err := h.getResourceInfoByID(ctx, client, share.ResourceId) - if err != nil || status.Code != rpc.Code_CODE_OK { - log.Debug().Interface("share", share).Interface("status", status).Err(err).Msg("could not stat share, skipping") - return - } + sData := conversions.PublicShare2ShareData(share, r, h.publicURL) - sData := conversions.PublicShare2ShareData(share, r, h.publicURL) + sData.Name = share.DisplayName - sData.Name = share.DisplayName + if err := h.addFileInfo(ctx, sData, info); err != nil { + log.Debug().Interface("share", share).Interface("info", info).Err(err).Msg("could not add file info, skipping") + continue + } + h.mapUserIds(ctx, client, sData) - if err := h.addFileInfo(ctx, sData, info); err != nil { - log.Debug().Interface("share", share).Interface("info", info).Err(err).Msg("could not add file info, skipping") - return - } - h.mapUserIds(ctx, client, sData) + log.Debug().Interface("share", share).Interface("info", info).Interface("shareData", share).Msg("mapped") - log.Debug().Interface("share", share).Interface("info", info).Interface("shareData", sData).Msg("mapped") - output <- sData - } - }(ctx, client, input, output, &wg) - } + ocsDataPayload = append(ocsDataPayload, sData) - for _, share := range res.Share { - input <- share - } - close(input) - wg.Wait() - close(output) + } - for s := range output { - ocsDataPayload = append(ocsDataPayload, s) + return ocsDataPayload, nil, nil } - return ocsDataPayload, nil, nil + return ocsDataPayload, nil, errors.New("bad request") } func (h *Handler) isPublicShare(r *http.Request, oid string) bool { diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go index c1d96250d5..bbfd776133 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares.go @@ -28,7 +28,6 @@ import ( "path" "strconv" "strings" - "sync" "text/template" "time" @@ -800,121 +799,101 @@ func (h *Handler) listSharesWithMe(w http.ResponseWriter, r *http.Request) { shares := make([]*conversions.ShareData, 0, len(lrsRes.GetShares())) - var wg sync.WaitGroup - workers := 50 - input := make(chan *collaboration.ReceivedShare, len(lrsRes.GetShares())) - output := make(chan *conversions.ShareData, len(lrsRes.GetShares())) + // TODO(refs) filter out "invalid" shares + for _, rs := range lrsRes.GetShares() { + if stateFilter != ocsStateUnknown && rs.GetState() != stateFilter { + continue + } + var info *provider.ResourceInfo + if pinfo != nil { + // check if the shared resource matches the path resource + if !utils.ResourceIDEqual(rs.Share.ResourceId, pinfo.Id) { + // try next share + continue + } + // we can reuse the stat info + info = pinfo + } else { + var status *rpc.Status + // FIXME the ResourceID is the id of the resource, but we want the id of the mount point so we can fetch that path, well we have the mountpoint path in the receivedshare + // first stat mount point + mountID := &provider.ResourceId{ + StorageId: utils.ShareStorageProviderID, + OpaqueId: rs.Share.Id.OpaqueId, + } + info, status, err = h.getResourceInfoByID(ctx, client, mountID) + if err != nil || status.Code != rpc.Code_CODE_OK { + // fallback to unmounted resource + info, status, err = h.getResourceInfoByID(ctx, client, rs.Share.ResourceId) + if err != nil || status.Code != rpc.Code_CODE_OK { + h.logProblems(status, err, "could not stat, skipping") + continue + } + } + } - for i := 0; i < workers; i++ { - wg.Add(1) - go func(ctx context.Context, client gateway.GatewayAPIClient, input chan *collaboration.ReceivedShare, output chan *conversions.ShareData, wg *sync.WaitGroup) { - defer wg.Done() + data, err := conversions.CS3Share2ShareData(r.Context(), rs.Share) + if err != nil { + log.Debug().Interface("share", rs.Share).Interface("shareData", data).Err(err).Msg("could not CS3Share2ShareData, skipping") + continue + } - for rs := range input { - if stateFilter != ocsStateUnknown && rs.GetState() != stateFilter { - return - } - var info *provider.ResourceInfo - if pinfo != nil { - // check if the shared resource matches the path resource - if !utils.ResourceIDEqual(rs.Share.ResourceId, pinfo.Id) { - // try next share - return - } - // we can reuse the stat info - info = pinfo - } else { - var status *rpc.Status - // FIXME the ResourceID is the id of the resource, but we want the id of the mount point so we can fetch that path, well we have the mountpoint path in the receivedshare - // first stat mount point - mountID := &provider.ResourceId{ - StorageId: utils.ShareStorageProviderID, - OpaqueId: rs.Share.Id.OpaqueId, - } - info, status, err = h.getResourceInfoByID(ctx, client, mountID) - if err != nil || status.Code != rpc.Code_CODE_OK { - // fallback to unmounted resource - info, status, err = h.getResourceInfoByID(ctx, client, rs.Share.ResourceId) - if err != nil || status.Code != rpc.Code_CODE_OK { - h.logProblems(status, err, "could not stat, skipping") - return - } - } - } + data.State = mapState(rs.GetState()) - data, err := conversions.CS3Share2ShareData(r.Context(), rs.Share) - if err != nil { - log.Debug().Interface("share", rs.Share).Interface("shareData", data).Err(err).Msg("could not CS3Share2ShareData, skipping") - return - } + if err := h.addFileInfo(ctx, data, info); err != nil { + log.Debug().Interface("received_share", rs).Interface("info", info).Interface("shareData", data).Err(err).Msg("could not add file info, skipping") + continue + } + h.mapUserIds(r.Context(), client, data) - data.State = mapState(rs.GetState()) + if data.State == ocsStateAccepted { + // only accepted shares can be accessed when jailing users into their home. + // in this case we cannot stat shared resources that are outside the users home (/home), + // the path (/users/u-u-i-d/foo) will not be accessible - if err := h.addFileInfo(ctx, data, info); err != nil { - log.Debug().Interface("received_share", rs).Interface("info", info).Interface("shareData", data).Err(err).Msg("could not add file info, skipping") - return - } - h.mapUserIds(r.Context(), client, data) - - if data.State == ocsStateAccepted { - // only accepted shares can be accessed when jailing users into their home. - // in this case we cannot stat shared resources that are outside the users home (/home), - // the path (/users/u-u-i-d/foo) will not be accessible - - // in a global namespace we can access the share using the full path - // in a jailed namespace we have to point to the mount point in the users /Shares jail - // - needed for oc10 hot migration - // or use the /dav/spaces/ endpoint? - - // list /Shares and match fileids with list of received shares - // - only works for a /Shares folder jail - // - does not work for freely mountable shares as in oc10 because we would need to iterate over the whole tree, there is no listing of mountpoints, yet - - // can we return the mountpoint when the gateway resolves the listing of shares? - // - no, the gateway only sees the same list any has the same options as the ocs service - // - we would need to have a list of mountpoints for the shares -> owncloudstorageprovider for hot migration migration - - // best we can do for now is stat the /Shares jail if it is set and return those paths - - // if we are in a jail and the current share has been accepted use the stat from the share jail - // Needed because received shares can be jailed in a folder in the users home - - if h.sharePrefix != "/" { - // if we have share jail infos use them to build the path - if rs.MountPoint != nil && rs.MountPoint.Path != "" { - // override path with info from share jail - data.FileTarget = path.Join(h.sharePrefix, rs.MountPoint.Path) - data.Path = path.Join(h.sharePrefix, rs.MountPoint.Path) - } else { - data.FileTarget = path.Join(h.sharePrefix, path.Base(info.Path)) - data.Path = path.Join(h.sharePrefix, path.Base(info.Path)) - } - } else { - data.FileTarget = info.Path - data.Path = info.Path - } - } else { - // not accepted shares need their Path jailed to make the testsuite happy + // in a global namespace we can access the share using the full path + // in a jailed namespace we have to point to the mount point in the users /Shares jail + // - needed for oc10 hot migration + // or use the /dav/spaces/ endpoint? - if h.sharePrefix != "/" { - data.Path = path.Join("/", path.Base(info.Path)) - } + // list /Shares and match fileids with list of received shares + // - only works for a /Shares folder jail + // - does not work for freely mountable shares as in oc10 because we would need to iterate over the whole tree, there is no listing of mountpoints, yet + + // can we return the mountpoint when the gateway resolves the listing of shares? + // - no, the gateway only sees the same list any has the same options as the ocs service + // - we would need to have a list of mountpoints for the shares -> owncloudstorageprovider for hot migration migration + + // best we can do for now is stat the /Shares jail if it is set and return those paths + // if we are in a jail and the current share has been accepted use the stat from the share jail + // Needed because received shares can be jailed in a folder in the users home + + if h.sharePrefix != "/" { + // if we have a mount point use it to build the path + if rs.MountPoint != nil && rs.MountPoint.Path != "" { + // override path with info from share jail + data.FileTarget = path.Join(h.sharePrefix, rs.MountPoint.Path) + data.Path = path.Join(h.sharePrefix, rs.MountPoint.Path) + } else { + data.FileTarget = path.Join(h.sharePrefix, path.Base(info.Path)) + data.Path = path.Join(h.sharePrefix, path.Base(info.Path)) } - output <- data + } else { + data.FileTarget = info.Path + data.Path = info.Path } - }(ctx, client, input, output, &wg) - } + } else { + // not accepted shares need their Path jailed to make the testsuite happy - for _, share := range lrsRes.GetShares() { - input <- share - } - close(input) - wg.Wait() - close(output) + if h.sharePrefix != "/" { + data.Path = path.Join("/", path.Base(info.Path)) + } + + } - for s := range output { - shares = append(shares, s) + shares = append(shares, data) + log.Debug().Msgf("share: %+v", *data) } response.WriteOCSSuccess(w, r, shares) diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go index e482810e32..335e0b9448 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/user.go @@ -19,11 +19,8 @@ package shares import ( - "context" "net/http" - "sync" - gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" @@ -193,65 +190,45 @@ func (h *Handler) listUserShares(r *http.Request, filters []*collaboration.Filte } ocsDataPayload := make([]*conversions.ShareData, 0) - client, err := h.getClient() - if err != nil { - return ocsDataPayload, nil, err - } - - // do list shares request. filtered - lsUserSharesResponse, err := client.ListShares(ctx, &lsUserSharesRequest) - if err != nil { - return ocsDataPayload, nil, err - } - if lsUserSharesResponse.Status.Code != rpc.Code_CODE_OK { - return ocsDataPayload, lsUserSharesResponse.Status, nil - } - - var wg sync.WaitGroup - workers := 50 - input := make(chan *collaboration.Share, len(lsUserSharesResponse.Shares)) - output := make(chan *conversions.ShareData, len(lsUserSharesResponse.Shares)) - - for i := 0; i < workers; i++ { - wg.Add(1) - go func(ctx context.Context, client gateway.GatewayAPIClient, input chan *collaboration.Share, output chan *conversions.ShareData, wg *sync.WaitGroup) { - defer wg.Done() - - // build OCS response payload - for s := range input { - data, err := conversions.CS3Share2ShareData(ctx, s) - if err != nil { - log.Debug().Interface("share", s).Interface("shareData", data).Err(err).Msg("could not CS3Share2ShareData, skipping") - return - } + if h.gatewayAddr != "" { + // get a connection to the users share provider + client, err := h.getClient() + if err != nil { + return ocsDataPayload, nil, err + } - info, status, err := h.getResourceInfoByID(ctx, client, s.ResourceId) - if err != nil || status.Code != rpc.Code_CODE_OK { - log.Debug().Interface("share", s).Interface("status", status).Interface("shareData", data).Err(err).Msg("could not stat share, skipping") - return - } + // do list shares request. filtered + lsUserSharesResponse, err := client.ListShares(ctx, &lsUserSharesRequest) + if err != nil { + return ocsDataPayload, nil, err + } + if lsUserSharesResponse.Status.Code != rpc.Code_CODE_OK { + return ocsDataPayload, lsUserSharesResponse.Status, nil + } - if err := h.addFileInfo(ctx, data, info); err != nil { - log.Debug().Interface("share", s).Interface("info", info).Interface("shareData", data).Err(err).Msg("could not add file info, skipping") - return - } - h.mapUserIds(ctx, client, data) + // build OCS response payload + for _, s := range lsUserSharesResponse.Shares { + data, err := conversions.CS3Share2ShareData(ctx, s) + if err != nil { + log.Debug().Interface("share", s).Interface("shareData", data).Err(err).Msg("could not CS3Share2ShareData, skipping") + continue + } - log.Debug().Interface("share", s).Interface("info", info).Interface("shareData", data).Msg("mapped") - output <- data + info, status, err := h.getResourceInfoByID(ctx, client, s.ResourceId) + if err != nil || status.Code != rpc.Code_CODE_OK { + log.Debug().Interface("share", s).Interface("status", status).Interface("shareData", data).Err(err).Msg("could not stat share, skipping") + continue } - }(ctx, client, input, output, &wg) - } - for _, share := range lsUserSharesResponse.Shares { - input <- share - } - close(input) - wg.Wait() - close(output) + if err := h.addFileInfo(ctx, data, info); err != nil { + log.Debug().Interface("share", s).Interface("info", info).Interface("shareData", data).Err(err).Msg("could not add file info, skipping") + continue + } + h.mapUserIds(ctx, client, data) - for s := range output { - ocsDataPayload = append(ocsDataPayload, s) + log.Debug().Interface("share", s).Interface("info", info).Interface("shareData", data).Msg("mapped") + ocsDataPayload = append(ocsDataPayload, data) + } } return ocsDataPayload, nil, nil diff --git a/pkg/storage/fs/nextcloud/nextcloud_server_mock.go b/pkg/storage/fs/nextcloud/nextcloud_server_mock.go index b6d06cc44f..06b71528f4 100644 --- a/pkg/storage/fs/nextcloud/nextcloud_server_mock.go +++ b/pkg/storage/fs/nextcloud/nextcloud_server_mock.go @@ -65,8 +65,8 @@ var responses = map[string]Response{ `POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/CreateHome `: {200, ``, serverStateHome}, `POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/CreateHome {}`: {200, ``, serverStateHome}, - `POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/CreateStorageSpace {"owner":{"id":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"username":"einstein"},"type":"personal","name":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c"}`: {200, `{"status":{"code":1}}`, serverStateHome}, - `POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/CreateStorageSpace {"owner":{"id":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"username":"einstein"},"type":"personal"}`: {200, `{"status":{"code":1}}`, serverStateHome}, + `POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/CreateStorageSpace {"owner":{"id":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"username":"einstein"},"type":"personal","name":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c"}`: {200, `{"status":{"code":1},"storage_space":{"id":{"opaque_id":"some-opaque-storage-space-id"},"owner":{"id":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}},"root":{"storage_id":"some-storage-ud","opaque_id":"some-opaque-root-id"},"name":"My Storage Space","quota":{"quota_max_bytes":456,"quota_max_files":123},"space_type":"home","mtime":{"seconds":1234567890}}}`, serverStateHome}, + `POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/CreateStorageSpace {"owner":{"id":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"username":"einstein"},"type":"personal"}`: {200, `{"status":{"code":1},"storage_space":{"id":{"opaque_id":"some-opaque-storage-space-id"},"owner":{"id":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}},"root":{"storage_id":"some-storage-ud","opaque_id":"some-opaque-root-id"},"name":"My Storage Space","quota":{"quota_max_bytes":456,"quota_max_files":123},"space_type":"home","mtime":{"seconds":1234567890}}}`, serverStateHome}, `POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/CreateReference {"path":"/Shares/reference","url":"scheme://target"}`: {200, `[]`, serverStateReference},