From 1b21aefbf8dbc83821dc12bb5e93702cd662fa5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Tue, 11 Jan 2022 16:03:47 +0100 Subject: [PATCH 01/49] use space reference when listing containers (#2432) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- changelog/unreleased/use-space-reference.md | 5 +++++ internal/http/services/owncloud/ocdav/propfind.go | 11 +++++------ 2 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 changelog/unreleased/use-space-reference.md diff --git a/changelog/unreleased/use-space-reference.md b/changelog/unreleased/use-space-reference.md new file mode 100644 index 00000000000..ca93f75a789 --- /dev/null +++ b/changelog/unreleased/use-space-reference.md @@ -0,0 +1,5 @@ +Bugfix: use space reference when listing containers + +The propfind handler now uses the reference for a space to make lookups relative. + +https://github.com/cs3org/reva/pull/2432 diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index cf7ebe3f930..9732e06ca73 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -258,6 +258,7 @@ func (s *svc) getResourceInfos(ctx context.Context, w http.ResponseWriter, r *ht var mostRecentChildInfo *provider.ResourceInfo var aggregatedChildSize uint64 spaceInfos := make([]*provider.ResourceInfo, 0, len(spaces)) + spaceMap := map[*provider.ResourceInfo]*provider.Reference{} for _, space := range spaces { if space.Opaque == nil || space.Opaque.Map == nil || space.Opaque.Map["path"] == nil || space.Opaque.Map["path"].Decoder != "plain" { continue // not mounted @@ -279,6 +280,7 @@ func (s *svc) getResourceInfos(ctx context.Context, w http.ResponseWriter, r *ht info.Path = filepath.Join(spacePath, spaceRef.Path) } + spaceMap[info] = spaceRef spaceInfos = append(spaceInfos, info) if rootInfo == nil && requestPath == info.Path || spacesPropfind && requestPath == path.Join("/", info.Path) { @@ -354,14 +356,10 @@ func (s *svc) getResourceInfos(ctx context.Context, w http.ResponseWriter, r *ht } case spaceInfo.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER && depth == "1": - switch { case strings.HasPrefix(requestPath, spaceInfo.Path): req := &provider.ListContainerRequest{ - Ref: &provider.Reference{ - ResourceId: spaceInfo.Id, - Path: ".", - }, + Ref: spaceMap[spaceInfo], ArbitraryMetadataKeys: metadataKeys, } res, err := client.ListContainer(ctx, req) @@ -418,7 +416,8 @@ func (s *svc) getResourceInfos(ctx context.Context, w http.ResponseWriter, r *ht req := &provider.ListContainerRequest{ Ref: &provider.Reference{ ResourceId: spaceInfo.Id, - Path: utils.MakeRelativePath(strings.TrimPrefix(info.Path, spaceInfo.Path)), + // TODO here we cut of the path that we added after stating the space above + Path: utils.MakeRelativePath(strings.TrimPrefix(info.Path, spaceInfo.Path)), }, ArbitraryMetadataKeys: metadataKeys, } From f83900210566829ff73055172e3902797dd5cff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Tue, 11 Jan 2022 17:03:07 +0100 Subject: [PATCH 02/49] fix shares provider filter (#2433) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- changelog/unreleased/fix-shares-provider-filter.md | 5 +++++ .../sharesstorageprovider/sharesstorageprovider.go | 9 +++++---- 2 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 changelog/unreleased/fix-shares-provider-filter.md diff --git a/changelog/unreleased/fix-shares-provider-filter.md b/changelog/unreleased/fix-shares-provider-filter.md new file mode 100644 index 00000000000..d60acf888a6 --- /dev/null +++ b/changelog/unreleased/fix-shares-provider-filter.md @@ -0,0 +1,5 @@ +Bugfix: fix shares provider filter + +The shares storage provider now correctly filters space types + +https://github.com/cs3org/reva/pull/2433 diff --git a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go index 816f8c4efa8..3a6f7166d35 100644 --- a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go +++ b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go @@ -317,14 +317,15 @@ func (s *service) ListStorageSpaces(ctx context.Context, req *provider.ListStora case provider.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE: spaceType := f.GetSpaceType() // do we need to fetch the shares? - if spaceType == "mountpoint" || spaceType == "grant" { - spaceTypes[spaceType] = exists - fetchShares = true - } if spaceType == "+mountpoint" || spaceType == "+grant" { appendTypes = append(appendTypes, strings.TrimPrefix(spaceType, "+")) fetchShares = true + continue + } + if spaceType == "mountpoint" || spaceType == "grant" { + fetchShares = true } + spaceTypes[spaceType] = exists case provider.ListStorageSpacesRequest_Filter_TYPE_ID: spaceid, shareid, err := utils.SplitStorageSpaceID(f.GetId().OpaqueId) if err != nil { From 07451f6cd8067b2b044f51928992924fbb925d20 Mon Sep 17 00:00:00 2001 From: Andre Duffeck Date: Wed, 12 Jan 2022 09:50:26 +0100 Subject: [PATCH 03/49] Start splitting up ocdav (#2434) * Start splitting up ocdav into smaller chunks That increases clarity and allows for making things testable. * Add a basic propfind unit test * Fix linter and hound issues * Add changelog --- changelog/unreleased/refactor-ocdav.md | 5 + go.mod | 2 +- go.sum | 2 + .../http/services/owncloud/ocdav/avatars.go | 3 +- internal/http/services/owncloud/ocdav/copy.go | 122 +- internal/http/services/owncloud/ocdav/dav.go | 52 +- .../http/services/owncloud/ocdav/delete.go | 46 +- .../owncloud/ocdav/{ => errors}/error.go | 30 +- internal/http/services/owncloud/ocdav/get.go | 59 +- internal/http/services/owncloud/ocdav/head.go | 44 +- .../http/services/owncloud/ocdav/mkcol.go | 55 +- internal/http/services/owncloud/ocdav/move.go | 70 +- .../services/owncloud/ocdav/net/context.go | 37 + .../services/owncloud/ocdav/net/headers.go | 58 + .../http/services/owncloud/ocdav/net/net.go | 80 + .../http/services/owncloud/ocdav/ocdav.go | 54 +- .../owncloud/ocdav/ocdav_suite_test.go | 31 + .../services/owncloud/ocdav/ocdav_test.go | 13 +- .../http/services/owncloud/ocdav/options.go | 18 +- .../ocdav/propfind/mocks/GatewayClient.go | 2735 +++++++++++++++++ .../owncloud/ocdav/{ => propfind}/propfind.go | 469 ++- .../ocdav/propfind/propfind_suite_test.go | 31 + .../owncloud/ocdav/propfind/propfind_test.go | 73 + .../http/services/owncloud/ocdav/proppatch.go | 135 +- .../services/owncloud/ocdav/props/props.go | 99 + .../services/owncloud/ocdav/publicfile.go | 12 +- internal/http/services/owncloud/ocdav/put.go | 83 +- .../http/services/owncloud/ocdav/report.go | 12 +- .../owncloud/ocdav/spacelookup/spacelookup.go | 153 + .../http/services/owncloud/ocdav/spaces.go | 150 +- .../http/services/owncloud/ocdav/trashbin.go | 212 +- internal/http/services/owncloud/ocdav/tus.go | 87 +- .../http/services/owncloud/ocdav/versions.go | 28 +- .../http/services/owncloud/ocdav/webdav.go | 55 +- .../ocs/handlers/cloud/users/users.go | 6 +- 35 files changed, 4102 insertions(+), 1019 deletions(-) create mode 100644 changelog/unreleased/refactor-ocdav.md rename internal/http/services/owncloud/ocdav/{ => errors}/error.go (87%) create mode 100644 internal/http/services/owncloud/ocdav/net/context.go create mode 100644 internal/http/services/owncloud/ocdav/net/headers.go create mode 100644 internal/http/services/owncloud/ocdav/net/net.go create mode 100644 internal/http/services/owncloud/ocdav/ocdav_suite_test.go create mode 100644 internal/http/services/owncloud/ocdav/propfind/mocks/GatewayClient.go rename internal/http/services/owncloud/ocdav/{ => propfind}/propfind.go (68%) create mode 100644 internal/http/services/owncloud/ocdav/propfind/propfind_suite_test.go create mode 100644 internal/http/services/owncloud/ocdav/propfind/propfind_test.go create mode 100644 internal/http/services/owncloud/ocdav/props/props.go create mode 100644 internal/http/services/owncloud/ocdav/spacelookup/spacelookup.go diff --git a/changelog/unreleased/refactor-ocdav.md b/changelog/unreleased/refactor-ocdav.md new file mode 100644 index 00000000000..89fb956f468 --- /dev/null +++ b/changelog/unreleased/refactor-ocdav.md @@ -0,0 +1,5 @@ +Enhancement: Refactor ocdav into smaller chunks + +That increases code clarity and enables testing. + +https://github.com/cs3org/reva/pull/2434 diff --git a/go.mod b/go.mod index b4ad8f6ecdb..38b1fae6b9e 100644 --- a/go.mod +++ b/go.mod @@ -57,7 +57,7 @@ require ( github.com/sethvargo/go-password v0.2.0 github.com/stretchr/objx v0.3.0 // indirect github.com/stretchr/testify v1.7.0 - github.com/studio-b12/gowebdav v0.0.0-20210917133250-a3a86976a1df + github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f github.com/thanhpk/randstr v1.0.4 github.com/tidwall/pretty v1.2.0 // indirect github.com/tus/tusd v1.8.0 diff --git a/go.sum b/go.sum index fc9a5475843..4307249b7a3 100644 --- a/go.sum +++ b/go.sum @@ -666,6 +666,8 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/studio-b12/gowebdav v0.0.0-20210917133250-a3a86976a1df h1:C+J/LwTqP8gRPt1MdSzBNZP0OYuDm5wsmDKgwpLjYzo= github.com/studio-b12/gowebdav v0.0.0-20210917133250-a3a86976a1df/go.mod h1:gCcfDlA1Y7GqOaeEKw5l9dOGx1VLdc/HuQSlQAaZ30s= +github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f h1:L2NE7BXnSlSLoNYZ0lCwZDjdnYjCNYC71k9ClZUTFTs= +github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/thanhpk/randstr v1.0.4 h1:IN78qu/bR+My+gHCvMEXhR/i5oriVHcTB/BJJIRTsNo= github.com/thanhpk/randstr v1.0.4/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U= diff --git a/internal/http/services/owncloud/ocdav/avatars.go b/internal/http/services/owncloud/ocdav/avatars.go index 2271d2901bd..6815d36ad39 100644 --- a/internal/http/services/owncloud/ocdav/avatars.go +++ b/internal/http/services/owncloud/ocdav/avatars.go @@ -22,6 +22,7 @@ import ( "encoding/hex" "net/http" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rhttp/router" ) @@ -57,7 +58,7 @@ func (h *AvatarsHandler) Handler(s *svc) http.Handler { log.Error().Err(err).Msg("error decoding string") w.WriteHeader(http.StatusInternalServerError) } - w.Header().Set(HeaderContentType, "image/png") + w.Header().Set(net.HeaderContentType, "image/png") if _, err := w.Write(decoded); err != nil { log.Error().Err(err).Msg("error writing data response") } diff --git a/internal/http/services/owncloud/ocdav/copy.go b/internal/http/services/owncloud/ocdav/copy.go index 350fb50a2c6..f3c94eb355a 100644 --- a/internal/http/services/owncloud/ocdav/copy.go +++ b/internal/http/services/owncloud/ocdav/copy.go @@ -32,6 +32,9 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/internal/http/services/datagateway" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rhttp" "github.com/cs3org/reva/pkg/rhttp/router" @@ -68,39 +71,39 @@ func (s *svc) handlePathCopy(w http.ResponseWriter, r *http.Request, ns string) sublog := appctx.GetLogger(ctx).With().Str("src", src).Str("dst", dst).Logger() - srcSpace, status, err := s.lookUpStorageSpaceForPath(ctx, src) + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } + + srcSpace, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, src) if err != nil { sublog.Error().Err(err).Str("path", src).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } - dstSpace, status, err := s.lookUpStorageSpaceForPath(ctx, dst) + dstSpace, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, dst) if err != nil { sublog.Error().Err(err).Str("path", dst).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } - cp := s.prepareCopy(ctx, w, r, makeRelativeReference(srcSpace, src, false), makeRelativeReference(dstSpace, dst, false), &sublog) + cp := s.prepareCopy(ctx, w, r, spacelookup.MakeRelativeReference(srcSpace, src, false), spacelookup.MakeRelativeReference(dstSpace, dst, false), &sublog) if cp == nil { return } - client, err := s.getClient() - if err != nil { - sublog.Error().Err(err).Msg("error getting grpc client") - w.WriteHeader(http.StatusInternalServerError) - return - } - if err := s.executePathCopy(ctx, client, w, r, cp); err != nil { sublog.Error().Err(err).Str("depth", cp.depth).Msg("error executing path copy") w.WriteHeader(http.StatusInternalServerError) @@ -125,11 +128,8 @@ func (s *svc) executePathCopy(ctx context.Context, client gateway.GatewayAPIClie if createRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { w.WriteHeader(http.StatusForbidden) m := fmt.Sprintf("Permission denied to create %v", createReq.Ref.Path) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: m, - }) - HandleWebdavError(log, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + errors.HandleWebdavError(log, w, b, err) } return nil } @@ -218,14 +218,11 @@ func (s *svc) executePathCopy(ctx context.Context, client gateway.GatewayAPIClie if uRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { w.WriteHeader(http.StatusForbidden) m := fmt.Sprintf("Permissions denied to create %v", uReq.Ref.Path) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: m, - }) - HandleWebdavError(log, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + errors.HandleWebdavError(log, w, b, err) return nil } - HandleErrorStatus(log, w, uRes.Status) + errors.HandleErrorStatus(log, w, uRes.Status) return nil } @@ -286,8 +283,15 @@ func (s *svc) handleSpacesCopy(w http.ResponseWriter, r *http.Request, spaceID s sublog := appctx.GetLogger(ctx).With().Str("spaceid", spaceID).Str("path", r.URL.Path).Str("destination", dst).Logger() + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } + // retrieve a specific storage space - srcRef, status, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path, true) + srcRef, status, err := spacelookup.LookUpStorageSpaceReference(ctx, client, spaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -295,14 +299,14 @@ func (s *svc) handleSpacesCopy(w http.ResponseWriter, r *http.Request, spaceID s } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } dstSpaceID, dstRelPath := router.ShiftPath(dst) // retrieve a specific storage space - dstRef, status, err := s.lookUpStorageSpaceReference(ctx, dstSpaceID, dstRelPath, true) + dstRef, status, err := spacelookup.LookUpStorageSpaceReference(ctx, client, dstSpaceID, dstRelPath, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -310,7 +314,7 @@ func (s *svc) handleSpacesCopy(w http.ResponseWriter, r *http.Request, spaceID s } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } @@ -318,12 +322,6 @@ func (s *svc) handleSpacesCopy(w http.ResponseWriter, r *http.Request, spaceID s if cp == nil { return } - client, err := s.getClient() - if err != nil { - sublog.Error().Err(err).Msg("error getting grpc client") - w.WriteHeader(http.StatusInternalServerError) - return - } err = s.executeSpacesCopy(ctx, w, client, cp) if err != nil { @@ -352,11 +350,8 @@ func (s *svc) executeSpacesCopy(ctx context.Context, w http.ResponseWriter, clie w.WriteHeader(http.StatusForbidden) // TODO path could be empty or relative... m := fmt.Sprintf("Permission denied to create %v", createReq.Ref.Path) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: m, - }) - HandleWebdavError(log, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + errors.HandleWebdavError(log, w, b, err) } return nil } @@ -412,7 +407,7 @@ func (s *svc) executeSpacesCopy(ctx context.Context, w http.ResponseWriter, clie Ref: cp.destination, Opaque: &typespb.Opaque{ Map: map[string]*typespb.OpaqueEntry{ - HeaderUploadLength: { + net.HeaderUploadLength: { Decoder: "plain", // TODO: handle case where size is not known in advance Value: []byte(strconv.FormatUint(cp.sourceInfo.GetSize(), 10)), @@ -431,14 +426,11 @@ func (s *svc) executeSpacesCopy(ctx context.Context, w http.ResponseWriter, clie w.WriteHeader(http.StatusForbidden) // TODO path can be empty or relative m := fmt.Sprintf("Permissions denied to create %v", uReq.Ref.Path) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: m, - }) - HandleWebdavError(log, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + errors.HandleWebdavError(log, w, b, err) return nil } - HandleErrorStatus(log, w, uRes.Status) + errors.HandleErrorStatus(log, w, uRes.Status) return nil } @@ -492,22 +484,16 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re if err != nil { w.WriteHeader(http.StatusBadRequest) m := fmt.Sprintf("Overwrite header is set to incorrect value %v", overwrite) - b, err := Marshal(exception{ - code: SabredavBadRequest, - message: m, - }) - HandleWebdavError(log, w, b, err) + b, err := errors.Marshal(errors.SabredavBadRequest, m, "") + errors.HandleWebdavError(log, w, b, err) return nil } depth, err := extractDepth(w, r) if err != nil { w.WriteHeader(http.StatusBadRequest) m := fmt.Sprintf("Depth header is set to incorrect value %v", depth) - b, err := Marshal(exception{ - code: SabredavBadRequest, - message: m, - }) - HandleWebdavError(log, w, b, err) + b, err := errors.Marshal(errors.SabredavBadRequest, m, "") + errors.HandleWebdavError(log, w, b, err) return nil } @@ -532,13 +518,10 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re if srcStatRes.Status.Code == rpc.Code_CODE_NOT_FOUND { w.WriteHeader(http.StatusNotFound) m := fmt.Sprintf("Resource %v not found", srcStatReq.Ref.Path) - b, err := Marshal(exception{ - code: SabredavNotFound, - message: m, - }) - HandleWebdavError(log, w, b, err) + b, err := errors.Marshal(errors.SabredavNotFound, m, "") + errors.HandleWebdavError(log, w, b, err) } - HandleErrorStatus(log, w, srcStatRes.Status) + errors.HandleErrorStatus(log, w, srcStatRes.Status) return nil } @@ -550,7 +533,7 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re return nil } if dstStatRes.Status.Code != rpc.Code_CODE_OK && dstStatRes.Status.Code != rpc.Code_CODE_NOT_FOUND { - HandleErrorStatus(log, w, srcStatRes.Status) + errors.HandleErrorStatus(log, w, srcStatRes.Status) return nil } @@ -562,11 +545,8 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re log.Warn().Str("overwrite", overwrite).Msg("dst already exists") w.WriteHeader(http.StatusPreconditionFailed) m := fmt.Sprintf("Could not overwrite Resource %v", dstRef.Path) - b, err := Marshal(exception{ - code: SabredavPreconditionFailed, - message: m, - }) - HandleWebdavError(log, w, b, err) // 412, see https://tools.ietf.org/html/rfc4918#section-9.8.5 + b, err := errors.Marshal(errors.SabredavPreconditionFailed, m, "") + errors.HandleWebdavError(log, w, b, err) // 412, see https://tools.ietf.org/html/rfc4918#section-9.8.5 return nil } @@ -580,7 +560,7 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re } if delRes.Status.Code != rpc.Code_CODE_OK && delRes.Status.Code != rpc.Code_CODE_NOT_FOUND { - HandleErrorStatus(log, w, delRes.Status) + errors.HandleErrorStatus(log, w, delRes.Status) return nil } } else if p := path.Dir(dstRef.Path); p != "" { @@ -602,7 +582,7 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re log.Debug().Interface("parent", pRef).Interface("status", intStatRes.Status).Msg("conflict") w.WriteHeader(http.StatusConflict) } else { - HandleErrorStatus(log, w, intStatRes.Status) + errors.HandleErrorStatus(log, w, intStatRes.Status) } return nil } @@ -613,7 +593,7 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re } func extractOverwrite(w http.ResponseWriter, r *http.Request) (string, error) { - overwrite := r.Header.Get(HeaderOverwrite) + overwrite := r.Header.Get(net.HeaderOverwrite) overwrite = strings.ToUpper(overwrite) if overwrite == "" { overwrite = "T" @@ -627,7 +607,7 @@ func extractOverwrite(w http.ResponseWriter, r *http.Request) (string, error) { } func extractDepth(w http.ResponseWriter, r *http.Request) (string, error) { - depth := r.Header.Get(HeaderDepth) + depth := r.Header.Get(net.HeaderDepth) if depth == "" { depth = "infinity" } diff --git a/internal/http/services/owncloud/ocdav/dav.go b/internal/http/services/owncloud/ocdav/dav.go index a830c399b87..def7ab68c5f 100644 --- a/internal/http/services/owncloud/ocdav/dav.go +++ b/internal/http/services/owncloud/ocdav/dav.go @@ -29,6 +29,8 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" "github.com/cs3org/reva/pkg/appctx" ctxpkg "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" @@ -110,10 +112,7 @@ func (h *DavHandler) Handler(s *svc) http.Handler { if r.Header.Get("Depth") == "" { w.WriteHeader(http.StatusMethodNotAllowed) - b, err := Marshal(exception{ - code: SabredavMethodNotAllowed, - message: "Listing members of this collection is disabled", - }) + b, err := errors.Marshal(errors.SabredavMethodNotAllowed, "Listing members of this collection is disabled", "") if err != nil { log.Error().Msgf("error marshaling xml response: %s", b) w.WriteHeader(http.StatusInternalServerError) @@ -146,37 +145,37 @@ func (h *DavHandler) Handler(s *svc) http.Handler { contextUser, ok := ctxpkg.ContextGetUser(ctx) if ok && isOwner(requestUserID, contextUser) { // use home storage handler when user was detected - base := path.Join(ctx.Value(ctxKeyBaseURI).(string), "files", requestUserID) - ctx := context.WithValue(ctx, ctxKeyBaseURI, base) + base := path.Join(ctx.Value(net.CtxKeyBaseURI).(string), "files", requestUserID) + ctx := context.WithValue(ctx, net.CtxKeyBaseURI, base) r = r.WithContext(ctx) h.FilesHomeHandler.Handler(s).ServeHTTP(w, r) } else { r.URL.Path = oldPath - base := path.Join(ctx.Value(ctxKeyBaseURI).(string), "files") - ctx := context.WithValue(ctx, ctxKeyBaseURI, base) + base := path.Join(ctx.Value(net.CtxKeyBaseURI).(string), "files") + ctx := context.WithValue(ctx, net.CtxKeyBaseURI, base) r = r.WithContext(ctx) h.FilesHandler.Handler(s).ServeHTTP(w, r) } case "meta": - base := path.Join(ctx.Value(ctxKeyBaseURI).(string), "meta") - ctx = context.WithValue(ctx, ctxKeyBaseURI, base) + base := path.Join(ctx.Value(net.CtxKeyBaseURI).(string), "meta") + ctx = context.WithValue(ctx, net.CtxKeyBaseURI, base) r = r.WithContext(ctx) h.MetaHandler.Handler(s).ServeHTTP(w, r) case "trash-bin": - base := path.Join(ctx.Value(ctxKeyBaseURI).(string), "trash-bin") - ctx := context.WithValue(ctx, ctxKeyBaseURI, base) + base := path.Join(ctx.Value(net.CtxKeyBaseURI).(string), "trash-bin") + ctx := context.WithValue(ctx, net.CtxKeyBaseURI, base) r = r.WithContext(ctx) h.TrashbinHandler.Handler(s).ServeHTTP(w, r) case "spaces": - base := path.Join(ctx.Value(ctxKeyBaseURI).(string), "spaces") - ctx := context.WithValue(ctx, ctxKeyBaseURI, base) + base := path.Join(ctx.Value(net.CtxKeyBaseURI).(string), "spaces") + ctx := context.WithValue(ctx, net.CtxKeyBaseURI, base) r = r.WithContext(ctx) h.SpacesHandler.Handler(s).ServeHTTP(w, r) case "public-files": - base := path.Join(ctx.Value(ctxKeyBaseURI).(string), "public-files") - ctx = context.WithValue(ctx, ctxKeyBaseURI, base) + base := path.Join(ctx.Value(net.CtxKeyBaseURI).(string), "public-files") + ctx = context.WithValue(ctx, net.CtxKeyBaseURI, base) c, err := pool.GetGatewayServiceClient(s.c.GatewaySvc) if err != nil { w.WriteHeader(http.StatusNotFound) @@ -209,18 +208,12 @@ func (h *DavHandler) Handler(s *svc) http.Handler { case res.Status.Code == rpcv1beta1.Code_CODE_UNAUTHENTICATED: w.WriteHeader(http.StatusUnauthorized) if hasValidBasicAuthHeader { - b, err := Marshal(exception{ - code: SabredavNotAuthenticated, - message: "Username or password was incorrect", - }) - HandleWebdavError(log, w, b, err) + b, err := errors.Marshal(errors.SabredavNotAuthenticated, "Username or password was incorrect", "") + errors.HandleWebdavError(log, w, b, err) return } - b, err := Marshal(exception{ - code: SabredavNotAuthenticated, - message: "No 'Authorization: Basic' header found", - }) - HandleWebdavError(log, w, b, err) + b, err := errors.Marshal(errors.SabredavNotAuthenticated, "No 'Authorization: Basic' header found", "") + errors.HandleWebdavError(log, w, b, err) return case res.Status.Code == rpcv1beta1.Code_CODE_NOT_FOUND: w.WriteHeader(http.StatusNotFound) @@ -270,11 +263,8 @@ func (h *DavHandler) Handler(s *svc) http.Handler { default: w.WriteHeader(http.StatusNotFound) - b, err := Marshal(exception{ - code: SabredavNotFound, - message: "File not found in root", - }) - HandleWebdavError(log, w, b, err) + b, err := errors.Marshal(errors.SabredavNotFound, "File not found in root", "") + errors.HandleWebdavError(log, w, b, err) } }) } diff --git a/internal/http/services/owncloud/ocdav/delete.go b/internal/http/services/owncloud/ocdav/delete.go index 7e2d954f820..c8d74248271 100644 --- a/internal/http/services/owncloud/ocdav/delete.go +++ b/internal/http/services/owncloud/ocdav/delete.go @@ -26,6 +26,8 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/pkg/appctx" rtrace "github.com/cs3org/reva/pkg/trace" "github.com/rs/zerolog" @@ -35,18 +37,24 @@ func (s *svc) handlePathDelete(w http.ResponseWriter, r *http.Request, ns string fn := path.Join(ns, r.URL.Path) sublog := appctx.GetLogger(r.Context()).With().Str("path", fn).Logger() - space, status, err := s.lookUpStorageSpaceForPath(r.Context(), fn) + client, err := s.getClient() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + space, status, err := spacelookup.LookUpStorageSpaceForPath(r.Context(), client, fn) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) return } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } - s.handleDelete(r.Context(), w, r, makeRelativeReference(space, fn, false), sublog) + s.handleDelete(r.Context(), w, r, spacelookup.MakeRelativeReference(space, fn, false), sublog) } func (s *svc) handleDelete(ctx context.Context, w http.ResponseWriter, r *http.Request, ref *provider.Reference, log zerolog.Logger) { @@ -72,32 +80,23 @@ func (s *svc) handleDelete(ctx context.Context, w http.ResponseWriter, r *http.R w.WriteHeader(http.StatusNotFound) // TODO path might be empty or relative... m := fmt.Sprintf("Resource %v not found", ref.Path) - b, err := Marshal(exception{ - code: SabredavNotFound, - message: m, - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavNotFound, m, "") + errors.HandleWebdavError(&log, w, b, err) } if res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { w.WriteHeader(http.StatusForbidden) // TODO path might be empty or relative... m := fmt.Sprintf("Permission denied to delete %v", ref.Path) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: m, - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + errors.HandleWebdavError(&log, w, b, err) } if res.Status.Code == rpc.Code_CODE_INTERNAL && res.Status.Message == "can't delete mount path" { w.WriteHeader(http.StatusForbidden) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: res.Status.Message, - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, res.Status.Message, "") + errors.HandleWebdavError(&log, w, b, err) } - HandleErrorStatus(&log, w, res.Status) + errors.HandleErrorStatus(&log, w, res.Status) return } @@ -110,9 +109,14 @@ func (s *svc) handleSpacesDelete(w http.ResponseWriter, r *http.Request, spaceID defer span.End() sublog := appctx.GetLogger(ctx).With().Logger() + client, err := s.getClient() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } // retrieve a specific storage space - ref, rpcStatus, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path, true) + ref, rpcStatus, err := spacelookup.LookUpStorageSpaceReference(ctx, client, spaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -120,7 +124,7 @@ func (s *svc) handleSpacesDelete(w http.ResponseWriter, r *http.Request, spaceID } if rpcStatus.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, rpcStatus) + errors.HandleErrorStatus(&sublog, w, rpcStatus) return } diff --git a/internal/http/services/owncloud/ocdav/error.go b/internal/http/services/owncloud/ocdav/errors/error.go similarity index 87% rename from internal/http/services/owncloud/ocdav/error.go rename to internal/http/services/owncloud/ocdav/errors/error.go index fec06430c50..bb395d21975 100644 --- a/internal/http/services/owncloud/ocdav/error.go +++ b/internal/http/services/owncloud/ocdav/errors/error.go @@ -16,7 +16,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -package ocdav +package errors import ( "encoding/xml" @@ -59,20 +59,21 @@ var ( } ) -type exception struct { - code code - message string - header string +// Exception represents a ocdav exception +type Exception struct { + Code code + Message string + Header string } // Marshal just calls the xml marshaller for a given exception. -func Marshal(e exception) ([]byte, error) { - xmlstring, err := xml.Marshal(&errorXML{ +func Marshal(code code, message string, header string) ([]byte, error) { + xmlstring, err := xml.Marshal(&ErrorXML{ Xmlnsd: "DAV", Xmlnss: "http://sabredav.org/ns", - Exception: codesEnum[e.code], - Message: e.message, - Header: e.header, + Exception: codesEnum[code], + Message: message, + Header: header, }) if err != nil { return []byte(""), err @@ -80,8 +81,9 @@ func Marshal(e exception) ([]byte, error) { return []byte(xml.Header + string(xmlstring)), err } +// ErrorXML holds the xml representation of an error // http://www.webdav.org/specs/rfc4918.html#ELEMENT_error -type errorXML struct { +type ErrorXML struct { XMLName xml.Name `xml:"d:error"` Xmlnsd string `xml:"xmlns:d,attr"` Xmlnss string `xml:"xmlns:s,attr"` @@ -92,7 +94,11 @@ type errorXML struct { Header string `xml:"s:header,omitempty"` } -var errInvalidPropfind = errors.New("webdav: invalid propfind") +// ErrorInvalidPropfind is an invalid propfind error +var ErrorInvalidPropfind = errors.New("webdav: invalid propfind") + +// ErrInvalidProppatch is an invalid proppatch error +var ErrInvalidProppatch = errors.New("webdav: invalid proppatch") // HandleErrorStatus checks the status code, logs a Debug or Error level message // and writes an appropriate http status diff --git a/internal/http/services/owncloud/ocdav/get.go b/internal/http/services/owncloud/ocdav/get.go index eea0d196738..80443edc79a 100644 --- a/internal/http/services/owncloud/ocdav/get.go +++ b/internal/http/services/owncloud/ocdav/get.go @@ -32,6 +32,9 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/internal/grpc/services/storageprovider" "github.com/cs3org/reva/internal/http/services/datagateway" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rhttp" rtrace "github.com/cs3org/reva/pkg/trace" @@ -48,18 +51,24 @@ func (s *svc) handlePathGet(w http.ResponseWriter, r *http.Request, ns string) { sublog := appctx.GetLogger(ctx).With().Str("path", fn).Str("svc", "ocdav").Str("handler", "get").Logger() - space, status, err := s.lookUpStorageSpaceForPath(ctx, fn) + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } + space, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, fn) if err != nil { sublog.Error().Err(err).Str("path", fn).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } - s.handleGet(ctx, w, r, makeRelativeReference(space, fn, false), "spaces", sublog) + s.handleGet(ctx, w, r, spacelookup.MakeRelativeReference(space, fn, false), "spaces", sublog) } func (s *svc) handleGet(ctx context.Context, w http.ResponseWriter, r *http.Request, ref *provider.Reference, dlProtocol string, log zerolog.Logger) { @@ -78,7 +87,7 @@ func (s *svc) handleGet(ctx context.Context, w http.ResponseWriter, r *http.Requ w.WriteHeader(http.StatusInternalServerError) return case sRes.Status.Code != rpc.Code_CODE_OK: - HandleErrorStatus(&log, w, sRes.Status) + errors.HandleErrorStatus(&log, w, sRes.Status) return case sRes.Info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER: log.Warn().Msg("resource is a folder and cannot be downloaded") @@ -93,7 +102,7 @@ func (s *svc) handleGet(ctx context.Context, w http.ResponseWriter, r *http.Requ w.WriteHeader(http.StatusInternalServerError) return } else if dRes.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&log, w, dRes.Status) + errors.HandleErrorStatus(&log, w, dRes.Status) return } @@ -112,8 +121,8 @@ func (s *svc) handleGet(ctx context.Context, w http.ResponseWriter, r *http.Requ } httpReq.Header.Set(datagateway.TokenTransportHeader, token) - if r.Header.Get(HeaderRange) != "" { - httpReq.Header.Set(HeaderRange, r.Header.Get(HeaderRange)) + if r.Header.Get(net.HeaderRange) != "" { + httpReq.Header.Set(net.HeaderRange, r.Header.Get(net.HeaderRange)) } httpClient := s.client @@ -133,34 +142,34 @@ func (s *svc) handleGet(ctx context.Context, w http.ResponseWriter, r *http.Requ info := sRes.Info - w.Header().Set(HeaderContentType, info.MimeType) - w.Header().Set(HeaderContentDisposistion, "attachment; filename*=UTF-8''"+ + w.Header().Set(net.HeaderContentType, info.MimeType) + w.Header().Set(net.HeaderContentDisposistion, "attachment; filename*=UTF-8''"+ path.Base(r.URL.Path)+"; filename=\""+path.Base(r.URL.Path)+"\"") - w.Header().Set(HeaderETag, info.Etag) - w.Header().Set(HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) - w.Header().Set(HeaderOCETag, info.Etag) + w.Header().Set(net.HeaderETag, info.Etag) + w.Header().Set(net.HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) + w.Header().Set(net.HeaderOCETag, info.Etag) t := utils.TSToTime(info.Mtime).UTC() lastModifiedString := t.Format(time.RFC1123Z) - w.Header().Set(HeaderLastModified, lastModifiedString) + w.Header().Set(net.HeaderLastModified, lastModifiedString) if httpRes.StatusCode == http.StatusPartialContent { - w.Header().Set(HeaderContentRange, httpRes.Header.Get(HeaderContentRange)) - w.Header().Set(HeaderContentLength, httpRes.Header.Get(HeaderContentLength)) + w.Header().Set(net.HeaderContentRange, httpRes.Header.Get(net.HeaderContentRange)) + w.Header().Set(net.HeaderContentLength, httpRes.Header.Get(net.HeaderContentLength)) w.WriteHeader(http.StatusPartialContent) } else { - w.Header().Set(HeaderContentLength, strconv.FormatUint(info.Size, 10)) + w.Header().Set(net.HeaderContentLength, strconv.FormatUint(info.Size, 10)) } if info.Checksum != nil { - w.Header().Set(HeaderOCChecksum, fmt.Sprintf("%s:%s", strings.ToUpper(string(storageprovider.GRPC2PKGXS(info.Checksum.Type))), info.Checksum.Sum)) + w.Header().Set(net.HeaderOCChecksum, fmt.Sprintf("%s:%s", strings.ToUpper(string(storageprovider.GRPC2PKGXS(info.Checksum.Type))), info.Checksum.Sum)) } var c int64 if c, err = io.Copy(w, httpRes.Body); err != nil { log.Error().Err(err).Msg("error finishing copying data to response") } - if httpRes.Header.Get(HeaderContentLength) != "" { - i, err := strconv.ParseInt(httpRes.Header.Get(HeaderContentLength), 10, 64) + if httpRes.Header.Get(net.HeaderContentLength) != "" { + i, err := strconv.ParseInt(httpRes.Header.Get(net.HeaderContentLength), 10, 64) if err != nil { - log.Error().Err(err).Str("content-length", httpRes.Header.Get(HeaderContentLength)).Msg("invalid content length in datagateway response") + log.Error().Err(err).Str("content-length", httpRes.Header.Get(net.HeaderContentLength)).Msg("invalid content length in datagateway response") } if i != c { log.Error().Int64("content-length", i).Int64("transferred-bytes", c).Msg("content length vs transferred bytes mismatch") @@ -174,9 +183,15 @@ func (s *svc) handleSpacesGet(w http.ResponseWriter, r *http.Request, spaceID st defer span.End() sublog := appctx.GetLogger(ctx).With().Str("path", r.URL.Path).Str("spaceid", spaceID).Str("handler", "get").Logger() + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } // retrieve a specific storage space - ref, rpcStatus, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path, true) + ref, rpcStatus, err := spacelookup.LookUpStorageSpaceReference(ctx, client, spaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -184,7 +199,7 @@ func (s *svc) handleSpacesGet(w http.ResponseWriter, r *http.Request, spaceID st } if rpcStatus.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, rpcStatus) + errors.HandleErrorStatus(&sublog, w, rpcStatus) return } s.handleGet(ctx, w, r, ref, "spaces", sublog) diff --git a/internal/http/services/owncloud/ocdav/head.go b/internal/http/services/owncloud/ocdav/head.go index 3b112c8a37a..3836ea17a56 100644 --- a/internal/http/services/owncloud/ocdav/head.go +++ b/internal/http/services/owncloud/ocdav/head.go @@ -33,6 +33,9 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/internal/grpc/services/storageprovider" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/utils" "github.com/rs/zerolog" @@ -45,18 +48,25 @@ func (s *svc) handlePathHead(w http.ResponseWriter, r *http.Request, ns string) fn := path.Join(ns, r.URL.Path) sublog := appctx.GetLogger(ctx).With().Str("path", fn).Logger() - space, status, err := s.lookUpStorageSpaceForPath(ctx, fn) + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } + + space, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, fn) if err != nil { sublog.Error().Err(err).Str("path", fn).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } - s.handleHead(ctx, w, r, makeRelativeReference(space, fn, false), sublog) + s.handleHead(ctx, w, r, spacelookup.MakeRelativeReference(space, fn, false), sublog) } func (s *svc) handleHead(ctx context.Context, w http.ResponseWriter, r *http.Request, ref *provider.Reference, log zerolog.Logger) { @@ -76,24 +86,24 @@ func (s *svc) handleHead(ctx context.Context, w http.ResponseWriter, r *http.Req } if res.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&log, w, res.Status) + errors.HandleErrorStatus(&log, w, res.Status) return } info := res.Info - w.Header().Set(HeaderContentType, info.MimeType) - w.Header().Set(HeaderETag, info.Etag) - w.Header().Set(HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) - w.Header().Set(HeaderOCETag, info.Etag) + w.Header().Set(net.HeaderContentType, info.MimeType) + w.Header().Set(net.HeaderETag, info.Etag) + w.Header().Set(net.HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) + w.Header().Set(net.HeaderOCETag, info.Etag) if info.Checksum != nil { - w.Header().Set(HeaderOCChecksum, fmt.Sprintf("%s:%s", strings.ToUpper(string(storageprovider.GRPC2PKGXS(info.Checksum.Type))), info.Checksum.Sum)) + w.Header().Set(net.HeaderOCChecksum, fmt.Sprintf("%s:%s", strings.ToUpper(string(storageprovider.GRPC2PKGXS(info.Checksum.Type))), info.Checksum.Sum)) } t := utils.TSToTime(info.Mtime).UTC() lastModifiedString := t.Format(time.RFC1123Z) - w.Header().Set(HeaderLastModified, lastModifiedString) - w.Header().Set(HeaderContentLength, strconv.FormatUint(info.Size, 10)) + w.Header().Set(net.HeaderLastModified, lastModifiedString) + w.Header().Set(net.HeaderContentLength, strconv.FormatUint(info.Size, 10)) if info.Type != provider.ResourceType_RESOURCE_TYPE_CONTAINER { - w.Header().Set(HeaderAcceptRanges, "bytes") + w.Header().Set(net.HeaderAcceptRanges, "bytes") } w.WriteHeader(http.StatusOK) } @@ -103,8 +113,14 @@ func (s *svc) handleSpacesHead(w http.ResponseWriter, r *http.Request, spaceID s defer span.End() sublog := appctx.GetLogger(ctx).With().Str("spaceid", spaceID).Str("path", r.URL.Path).Logger() + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } - spaceRef, status, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path, true) + spaceRef, status, err := spacelookup.LookUpStorageSpaceReference(ctx, client, spaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -112,7 +128,7 @@ func (s *svc) handleSpacesHead(w http.ResponseWriter, r *http.Request, spaceID s } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } diff --git a/internal/http/services/owncloud/ocdav/mkcol.go b/internal/http/services/owncloud/ocdav/mkcol.go index 1df68122762..72d57acb5f0 100644 --- a/internal/http/services/owncloud/ocdav/mkcol.go +++ b/internal/http/services/owncloud/ocdav/mkcol.go @@ -26,6 +26,8 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/pkg/appctx" rtrace "github.com/cs3org/reva/pkg/trace" "github.com/rs/zerolog" @@ -43,21 +45,27 @@ func (s *svc) handlePathMkcol(w http.ResponseWriter, r *http.Request, ns string) } } sublog := appctx.GetLogger(ctx).With().Str("path", fn).Logger() + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } parentPath := path.Dir(fn) - space, status, err := s.lookUpStorageSpaceForPath(ctx, parentPath) + space, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, parentPath) if err != nil { sublog.Error().Err(err).Str("path", parentPath).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } - s.handleMkcol(ctx, w, r, makeRelativeReference(space, parentPath, false), makeRelativeReference(space, fn, false), sublog) + s.handleMkcol(ctx, w, r, spacelookup.MakeRelativeReference(space, parentPath, false), spacelookup.MakeRelativeReference(space, fn, false), sublog) } func (s *svc) handleSpacesMkCol(w http.ResponseWriter, r *http.Request, spaceID string) { @@ -65,8 +73,14 @@ func (s *svc) handleSpacesMkCol(w http.ResponseWriter, r *http.Request, spaceID defer span.End() sublog := appctx.GetLogger(ctx).With().Str("path", r.URL.Path).Str("spaceid", spaceID).Str("handler", "mkcol").Logger() + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } - parentRef, rpcStatus, err := s.lookUpStorageSpaceReference(ctx, spaceID, path.Dir(r.URL.Path), true) + parentRef, rpcStatus, err := spacelookup.LookUpStorageSpaceReference(ctx, client, spaceID, path.Dir(r.URL.Path), true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -74,11 +88,11 @@ func (s *svc) handleSpacesMkCol(w http.ResponseWriter, r *http.Request, spaceID } if rpcStatus.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, rpcStatus) + errors.HandleErrorStatus(&sublog, w, rpcStatus) return } - childRef, rpcStatus, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path, true) + childRef, rpcStatus, err := spacelookup.LookUpStorageSpaceReference(ctx, client, spaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -86,7 +100,7 @@ func (s *svc) handleSpacesMkCol(w http.ResponseWriter, r *http.Request, spaceID } if rpcStatus.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, rpcStatus) + errors.HandleErrorStatus(&sublog, w, rpcStatus) return } @@ -123,13 +137,10 @@ func (s *svc) handleMkcol(ctx context.Context, w http.ResponseWriter, r *http.Re // all ancestors must already exist, or the method must fail // with a 409 (Conflict) status code. w.WriteHeader(http.StatusConflict) - b, err := Marshal(exception{ - code: SabredavNotFound, - message: "Parent node does not exist", - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavNotFound, "Parent node does not exist", "") + errors.HandleWebdavError(&log, w, b, err) } else { - HandleErrorStatus(&log, w, parentStatRes.Status) + errors.HandleErrorStatus(&log, w, parentStatRes.Status) } return } @@ -146,13 +157,10 @@ func (s *svc) handleMkcol(ctx context.Context, w http.ResponseWriter, r *http.Re if statRes.Status.Code != rpc.Code_CODE_NOT_FOUND { if statRes.Status.Code == rpc.Code_CODE_OK { w.WriteHeader(http.StatusMethodNotAllowed) // 405 if it already exists - b, err := Marshal(exception{ - code: SabredavMethodNotAllowed, - message: "The resource you tried to create already exists", - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavMethodNotAllowed, "The resource you tried to create already exists", "") + errors.HandleWebdavError(&log, w, b, err) } else { - HandleErrorStatus(&log, w, statRes.Status) + errors.HandleErrorStatus(&log, w, statRes.Status) } return } @@ -174,12 +182,9 @@ func (s *svc) handleMkcol(ctx context.Context, w http.ResponseWriter, r *http.Re w.WriteHeader(http.StatusForbidden) // TODO path could be empty or relative... m := fmt.Sprintf("Permission denied to create %v", childRef.Path) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: m, - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + errors.HandleWebdavError(&log, w, b, err) default: - HandleErrorStatus(&log, w, res.Status) + errors.HandleErrorStatus(&log, w, res.Status) } } diff --git a/internal/http/services/owncloud/ocdav/move.go b/internal/http/services/owncloud/ocdav/move.go index cf7f382531c..d68ffff23e8 100644 --- a/internal/http/services/owncloud/ocdav/move.go +++ b/internal/http/services/owncloud/ocdav/move.go @@ -27,6 +27,9 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rhttp/router" rtrace "github.com/cs3org/reva/pkg/trace" @@ -56,25 +59,31 @@ func (s *svc) handlePathMove(w http.ResponseWriter, r *http.Request, ns string) dstPath = path.Join(ns, dstPath) sublog := appctx.GetLogger(ctx).With().Str("src", srcPath).Str("dst", dstPath).Logger() + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } - srcSpace, status, err := s.lookUpStorageSpaceForPath(ctx, srcPath) + srcSpace, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, srcPath) if err != nil { sublog.Error().Err(err).Str("path", srcPath).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } - dstSpace, status, err := s.lookUpStorageSpaceForPath(ctx, dstPath) + dstSpace, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, dstPath) if err != nil { sublog.Error().Err(err).Str("path", srcPath).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } @@ -83,7 +92,7 @@ func (s *svc) handlePathMove(w http.ResponseWriter, r *http.Request, ns string) dstSpace.Root = srcSpace.Root } - s.handleMove(ctx, w, r, makeRelativeReference(srcSpace, srcPath, false), makeRelativeReference(dstSpace, dstPath, false), sublog) + s.handleMove(ctx, w, r, spacelookup.MakeRelativeReference(srcSpace, srcPath, false), spacelookup.MakeRelativeReference(dstSpace, dstPath, false), sublog) } func (s *svc) handleSpacesMove(w http.ResponseWriter, r *http.Request, srcSpaceID string) { @@ -97,8 +106,15 @@ func (s *svc) handleSpacesMove(w http.ResponseWriter, r *http.Request, srcSpaceI } sublog := appctx.GetLogger(ctx).With().Str("spaceid", srcSpaceID).Str("path", r.URL.Path).Logger() + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } + // retrieve a specific storage space - srcRef, status, err := s.lookUpStorageSpaceReference(ctx, srcSpaceID, r.URL.Path, true) + srcRef, status, err := spacelookup.LookUpStorageSpaceReference(ctx, client, srcSpaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -106,14 +122,14 @@ func (s *svc) handleSpacesMove(w http.ResponseWriter, r *http.Request, srcSpaceI } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } dstSpaceID, dstRelPath := router.ShiftPath(dst) // retrieve a specific storage space - dstRef, status, err := s.lookUpStorageSpaceReference(ctx, dstSpaceID, dstRelPath, true) + dstRef, status, err := spacelookup.LookUpStorageSpaceReference(ctx, client, dstSpaceID, dstRelPath, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -121,7 +137,7 @@ func (s *svc) handleSpacesMove(w http.ResponseWriter, r *http.Request, srcSpaceI } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } @@ -129,7 +145,7 @@ func (s *svc) handleSpacesMove(w http.ResponseWriter, r *http.Request, srcSpaceI } func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Request, src, dst *provider.Reference, log zerolog.Logger) { - overwrite := r.Header.Get(HeaderOverwrite) + overwrite := r.Header.Get(net.HeaderOverwrite) log.Debug().Str("overwrite", overwrite).Msg("move") overwrite = strings.ToUpper(overwrite) @@ -161,13 +177,10 @@ func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Req if srcStatRes.Status.Code == rpc.Code_CODE_NOT_FOUND { w.WriteHeader(http.StatusNotFound) m := fmt.Sprintf("Resource %v not found", srcStatReq.Ref.Path) - b, err := Marshal(exception{ - code: SabredavNotFound, - message: m, - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavNotFound, m, "") + errors.HandleWebdavError(&log, w, b, err) } - HandleErrorStatus(&log, w, srcStatRes.Status) + errors.HandleErrorStatus(&log, w, srcStatRes.Status) return } @@ -180,7 +193,7 @@ func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Req return } if dstStatRes.Status.Code != rpc.Code_CODE_OK && dstStatRes.Status.Code != rpc.Code_CODE_NOT_FOUND { - HandleErrorStatus(&log, w, srcStatRes.Status) + errors.HandleErrorStatus(&log, w, srcStatRes.Status) return } @@ -204,7 +217,7 @@ func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Req } if delRes.Status.Code != rpc.Code_CODE_OK && delRes.Status.Code != rpc.Code_CODE_NOT_FOUND { - HandleErrorStatus(&log, w, delRes.Status) + errors.HandleErrorStatus(&log, w, delRes.Status) return } } else { @@ -225,7 +238,7 @@ func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Req log.Debug().Interface("parent", dst).Interface("status", intStatRes.Status).Msg("conflict") w.WriteHeader(http.StatusConflict) } else { - HandleErrorStatus(&log, w, intStatRes.Status) + errors.HandleErrorStatus(&log, w, intStatRes.Status) } return } @@ -244,13 +257,10 @@ func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Req if mRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { w.WriteHeader(http.StatusForbidden) m := fmt.Sprintf("Permission denied to move %v", src.Path) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: m, - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + errors.HandleWebdavError(&log, w, b, err) } - HandleErrorStatus(&log, w, mRes.Status) + errors.HandleErrorStatus(&log, w, mRes.Status) return } @@ -262,14 +272,14 @@ func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Req } if dstStatRes.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&log, w, dstStatRes.Status) + errors.HandleErrorStatus(&log, w, dstStatRes.Status) return } info := dstStatRes.Info - w.Header().Set(HeaderContentType, info.MimeType) - w.Header().Set(HeaderETag, info.Etag) - w.Header().Set(HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) - w.Header().Set(HeaderOCETag, info.Etag) + w.Header().Set(net.HeaderContentType, info.MimeType) + w.Header().Set(net.HeaderETag, info.Etag) + w.Header().Set(net.HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) + w.Header().Set(net.HeaderOCETag, info.Etag) w.WriteHeader(successCode) } diff --git a/internal/http/services/owncloud/ocdav/net/context.go b/internal/http/services/owncloud/ocdav/net/context.go new file mode 100644 index 00000000000..d3069e89724 --- /dev/null +++ b/internal/http/services/owncloud/ocdav/net/context.go @@ -0,0 +1,37 @@ +// Copyright 2018-2022 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package net + +import ( + "context" + + userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + ctxpkg "github.com/cs3org/reva/pkg/ctx" +) + +// IsCurrentUserOwner returns whether the context user is the given owner or not +func IsCurrentUserOwner(ctx context.Context, owner *userv1beta1.UserId) bool { + contextUser, ok := ctxpkg.ContextGetUser(ctx) + if ok && contextUser.Id != nil && owner != nil && + contextUser.Id.Idp == owner.Idp && + contextUser.Id.OpaqueId == owner.OpaqueId { + return true + } + return false +} diff --git a/internal/http/services/owncloud/ocdav/net/headers.go b/internal/http/services/owncloud/ocdav/net/headers.go new file mode 100644 index 00000000000..657ad770202 --- /dev/null +++ b/internal/http/services/owncloud/ocdav/net/headers.go @@ -0,0 +1,58 @@ +// Copyright 2018-2022 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package net + +// Common HTTP headers. +const ( + HeaderAcceptRanges = "Accept-Ranges" + HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers" + HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers" + HeaderContentDisposistion = "Content-Disposition" + HeaderContentLength = "Content-Length" + HeaderContentRange = "Content-Range" + HeaderContentType = "Content-Type" + HeaderETag = "ETag" + HeaderLastModified = "Last-Modified" + HeaderLocation = "Location" + HeaderRange = "Range" + HeaderIfMatch = "If-Match" +) + +// Non standard HTTP headers. +const ( + HeaderOCFileID = "OC-FileId" + HeaderOCETag = "OC-ETag" + HeaderOCChecksum = "OC-Checksum" + HeaderOCPermissions = "OC-Perm" + HeaderDepth = "Depth" + HeaderDav = "DAV" + HeaderTusResumable = "Tus-Resumable" + HeaderTusVersion = "Tus-Version" + HeaderTusExtension = "Tus-Extension" + HeaderTusChecksumAlgorithm = "Tus-Checksum-Algorithm" + HeaderTusUploadExpires = "Upload-Expires" + HeaderDestination = "Destination" + HeaderOverwrite = "Overwrite" + HeaderUploadChecksum = "Upload-Checksum" + HeaderUploadLength = "Upload-Length" + HeaderUploadMetadata = "Upload-Metadata" + HeaderUploadOffset = "Upload-Offset" + HeaderOCMtime = "X-OC-Mtime" + HeaderExpectedEntityLength = "X-Expected-Entity-Length" +) diff --git a/internal/http/services/owncloud/ocdav/net/net.go b/internal/http/services/owncloud/ocdav/net/net.go new file mode 100644 index 00000000000..e5f3ded8dc5 --- /dev/null +++ b/internal/http/services/owncloud/ocdav/net/net.go @@ -0,0 +1,80 @@ +// Copyright 2018-2022 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package net + +import ( + "fmt" + "regexp" + "strings" +) + +type ctxKey int + +const ( + // CtxKeyBaseURI is the key of the base URI context field + CtxKeyBaseURI ctxKey = iota + + // NsDav is the Dav ns + NsDav = "DAV:" + // NsOwncloud is the owncloud ns + NsOwncloud = "http://owncloud.org/ns" + // NsOCS is the OCS ns + NsOCS = "http://open-collaboration-services.org/ns" + + // RFC1123 time that mimics oc10. time.RFC1123 would end in "UTC", see https://github.com/golang/go/issues/13781 + RFC1123 = "Mon, 02 Jan 2006 15:04:05 GMT" + + // PropQuotaUnknown is the quota unknown property + PropQuotaUnknown = "-2" + // PropOcFavorite is the favorite ns property + PropOcFavorite = "http://owncloud.org/ns/favorite" +) + +// replaceAllStringSubmatchFunc is taken from 'Go: Replace String with Regular Expression Callback' +// see: https://elliotchance.medium.com/go-replace-string-with-regular-expression-callback-f89948bad0bb +func replaceAllStringSubmatchFunc(re *regexp.Regexp, str string, repl func([]string) string) string { + result := "" + lastIndex := 0 + for _, v := range re.FindAllSubmatchIndex([]byte(str), -1) { + groups := []string{} + for i := 0; i < len(v); i += 2 { + groups = append(groups, str[v[i]:v[i+1]]) + } + result += str[lastIndex:v[0]] + repl(groups) + lastIndex = v[1] + } + return result + str[lastIndex:] +} + +var hrefre = regexp.MustCompile(`([^A-Za-z0-9_\-.~()/:@!$])`) + +// EncodePath encodes the path of a url. +// +// slashes (/) are treated as path-separators. +// ported from https://github.com/sabre-io/http/blob/bb27d1a8c92217b34e778ee09dcf79d9a2936e84/lib/functions.php#L369-L379 +func EncodePath(path string) string { + return replaceAllStringSubmatchFunc(hrefre, path, func(groups []string) string { + b := groups[1] + var sb strings.Builder + for i := 0; i < len(b); i++ { + sb.WriteString(fmt.Sprintf("%%%x", b[i])) + } + return sb.String() + }) +} diff --git a/internal/http/services/owncloud/ocdav/ocdav.go b/internal/http/services/owncloud/ocdav/ocdav.go index 185ad25c943..ae2e5ac570c 100644 --- a/internal/http/services/owncloud/ocdav/ocdav.go +++ b/internal/http/services/owncloud/ocdav/ocdav.go @@ -20,17 +20,16 @@ package ocdav import ( "context" - "fmt" "net/http" "net/url" "path" - "regexp" "strings" "time" 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" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" "github.com/cs3org/reva/pkg/appctx" ctxpkg "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/errtypes" @@ -47,12 +46,6 @@ import ( "github.com/rs/zerolog" ) -type ctxKey int - -const ( - ctxKeyBaseURI ctxKey = iota -) - var ( errInvalidValue = errors.New("invalid value") @@ -123,6 +116,10 @@ type svc struct { client *http.Client } +func (s *svc) Config() *Config { + return s.c +} + func getFavoritesManager(c *Config) (favorite.Manager, error) { if f, ok := registry.NewFuncs[c.FavoriteStorageDriver]; ok { return f(c.FavoriteStorageDrivers[c.FavoriteStorageDriver]) @@ -228,7 +225,7 @@ func (s *svc) Handler() http.Handler { // for oc we need to prepend /home as the path that will be passed to the home storage provider // will not contain the username base = path.Join(base, "webdav") - ctx := context.WithValue(ctx, ctxKeyBaseURI, base) + ctx := context.WithValue(ctx, net.CtxKeyBaseURI, base) r = r.WithContext(ctx) s.webDavHandler.Handler(s).ServeHTTP(w, r) return @@ -238,7 +235,7 @@ func (s *svc) Handler() http.Handler { // for oc we need to prepend the path to user homes // or we take the path starting at /dav and allow rewriting it? base = path.Join(base, "dav") - ctx := context.WithValue(ctx, ctxKeyBaseURI, base) + ctx := context.WithValue(ctx, net.CtxKeyBaseURI, base) r = r.WithContext(ctx) s.davHandler.Handler(s).ServeHTTP(w, r) return @@ -328,7 +325,7 @@ func addAccessHeaders(w http.ResponseWriter, r *http.Request) { } func extractDestination(r *http.Request) (string, error) { - dstHeader := r.Header.Get(HeaderDestination) + dstHeader := r.Header.Get(net.HeaderDestination) if dstHeader == "" { return "", errors.Wrap(errInvalidValue, "destination header is empty") } @@ -337,7 +334,7 @@ func extractDestination(r *http.Request) (string, error) { return "", errors.Wrap(errInvalidValue, err.Error()) } - baseURI := r.Context().Value(ctxKeyBaseURI).(string) + baseURI := r.Context().Value(net.CtxKeyBaseURI).(string) // TODO check if path is on same storage, return 502 on problems, see https://tools.ietf.org/html/rfc4918#section-9.9.4 // Strip the base URI from the destination. The destination might contain redirection prefixes which need to be handled urlSplit := strings.Split(dstURL.Path, baseURI) @@ -347,36 +344,3 @@ func extractDestination(r *http.Request) (string, error) { return urlSplit[1], nil } - -// replaceAllStringSubmatchFunc is taken from 'Go: Replace String with Regular Expression Callback' -// see: https://elliotchance.medium.com/go-replace-string-with-regular-expression-callback-f89948bad0bb -func replaceAllStringSubmatchFunc(re *regexp.Regexp, str string, repl func([]string) string) string { - result := "" - lastIndex := 0 - for _, v := range re.FindAllSubmatchIndex([]byte(str), -1) { - groups := []string{} - for i := 0; i < len(v); i += 2 { - groups = append(groups, str[v[i]:v[i+1]]) - } - result += str[lastIndex:v[0]] + repl(groups) - lastIndex = v[1] - } - return result + str[lastIndex:] -} - -var hrefre = regexp.MustCompile(`([^A-Za-z0-9_\-.~()/:@!$])`) - -// encodePath encodes the path of a url. -// -// slashes (/) are treated as path-separators. -// ported from https://github.com/sabre-io/http/blob/bb27d1a8c92217b34e778ee09dcf79d9a2936e84/lib/functions.php#L369-L379 -func encodePath(path string) string { - return replaceAllStringSubmatchFunc(hrefre, path, func(groups []string) string { - b := groups[1] - var sb strings.Builder - for i := 0; i < len(b); i++ { - sb.WriteString(fmt.Sprintf("%%%x", b[i])) - } - return sb.String() - }) -} diff --git a/internal/http/services/owncloud/ocdav/ocdav_suite_test.go b/internal/http/services/owncloud/ocdav/ocdav_suite_test.go new file mode 100644 index 00000000000..c31853b080f --- /dev/null +++ b/internal/http/services/owncloud/ocdav/ocdav_suite_test.go @@ -0,0 +1,31 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package ocdav_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestOcdav(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Ocdav Suite") +} diff --git a/internal/http/services/owncloud/ocdav/ocdav_test.go b/internal/http/services/owncloud/ocdav/ocdav_test.go index 7d0de23167d..98c60afa33c 100644 --- a/internal/http/services/owncloud/ocdav/ocdav_test.go +++ b/internal/http/services/owncloud/ocdav/ocdav_test.go @@ -25,6 +25,7 @@ import ( "testing" providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" "github.com/cs3org/reva/pkg/utils/resourceid" ) @@ -37,7 +38,7 @@ then this method alone will cost a huge amount of time. */ func BenchmarkEncodePath(b *testing.B) { for i := 0; i < b.N; i++ { - _ = encodePath("/some/path/Folder %^*(#1)") + _ = net.EncodePath("/some/path/Folder %^*(#1)") } } @@ -53,9 +54,9 @@ func TestWrapResourceID(t *testing.T) { func TestExtractDestination(t *testing.T) { expected := "/dst" request := httptest.NewRequest(http.MethodGet, "https://example.org/remote.php/dav/src", nil) - request.Header.Set(HeaderDestination, "https://example.org/remote.php/dav/dst") + request.Header.Set(net.HeaderDestination, "https://example.org/remote.php/dav/dst") - ctx := context.WithValue(context.Background(), ctxKeyBaseURI, "remote.php/dav") + ctx := context.WithValue(context.Background(), net.CtxKeyBaseURI, "remote.php/dav") destination, err := extractDestination(request.WithContext(ctx)) if err != nil { t.Errorf("Expected err to be nil got %s", err) @@ -81,7 +82,7 @@ func TestExtractDestinationWithoutHeader(t *testing.T) { func TestExtractDestinationWithInvalidDestination(t *testing.T) { request := httptest.NewRequest(http.MethodGet, "https://example.org/remote.php/dav/src", nil) - request.Header.Set(HeaderDestination, "://example.org/remote.php/dav/dst") + request.Header.Set(net.HeaderDestination, "://example.org/remote.php/dav/dst") _, err := extractDestination(request) if err == nil { t.Errorf("Expected err to be nil got %s", err) @@ -94,9 +95,9 @@ func TestExtractDestinationWithInvalidDestination(t *testing.T) { func TestExtractDestinationWithDestinationWrongBaseURI(t *testing.T) { request := httptest.NewRequest(http.MethodGet, "https://example.org/remote.php/dav/src", nil) - request.Header.Set(HeaderDestination, "https://example.org/remote.php/dav/dst") + request.Header.Set(net.HeaderDestination, "https://example.org/remote.php/dav/dst") - ctx := context.WithValue(context.Background(), ctxKeyBaseURI, "remote.php/webdav") + ctx := context.WithValue(context.Background(), net.CtxKeyBaseURI, "remote.php/webdav") _, err := extractDestination(request.WithContext(ctx)) if err == nil { t.Errorf("Expected err to be nil got %s", err) diff --git a/internal/http/services/owncloud/ocdav/options.go b/internal/http/services/owncloud/ocdav/options.go index e2db6fe9ae2..abe4917cf75 100644 --- a/internal/http/services/owncloud/ocdav/options.go +++ b/internal/http/services/owncloud/ocdav/options.go @@ -21,6 +21,8 @@ package ocdav import ( "net/http" "strings" + + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" ) func (s *svc) handleOptions(w http.ResponseWriter, r *http.Request) { @@ -28,19 +30,19 @@ func (s *svc) handleOptions(w http.ResponseWriter, r *http.Request) { allow += " MOVE, UNLOCK, PROPFIND, MKCOL, REPORT, SEARCH," allow += " PUT" // TODO(jfd): only for files ... but we cannot create the full path without a user ... which we only have when credentials are sent - isPublic := strings.Contains(r.Context().Value(ctxKeyBaseURI).(string), "public-files") + isPublic := strings.Contains(r.Context().Value(net.CtxKeyBaseURI).(string), "public-files") - w.Header().Set(HeaderContentType, "application/xml") + w.Header().Set(net.HeaderContentType, "application/xml") w.Header().Set("Allow", allow) w.Header().Set("DAV", "1, 2") w.Header().Set("MS-Author-Via", "DAV") if !isPublic { - w.Header().Add(HeaderAccessControlAllowHeaders, HeaderTusResumable) - w.Header().Add(HeaderAccessControlExposeHeaders, strings.Join([]string{HeaderTusResumable, HeaderTusVersion, HeaderTusExtension}, ",")) - w.Header().Set(HeaderTusResumable, "1.0.0") // TODO(jfd): only for dirs? - w.Header().Set(HeaderTusVersion, "1.0.0") - w.Header().Set(HeaderTusExtension, "creation,creation-with-upload,checksum,expiration") - w.Header().Set(HeaderTusChecksumAlgorithm, "md5,sha1,crc32") + w.Header().Add(net.HeaderAccessControlAllowHeaders, net.HeaderTusResumable) + w.Header().Add(net.HeaderAccessControlExposeHeaders, strings.Join([]string{net.HeaderTusResumable, net.HeaderTusVersion, net.HeaderTusExtension}, ",")) + w.Header().Set(net.HeaderTusResumable, "1.0.0") // TODO(jfd): only for dirs? + w.Header().Set(net.HeaderTusVersion, "1.0.0") + w.Header().Set(net.HeaderTusExtension, "creation,creation-with-upload,checksum,expiration") + w.Header().Set(net.HeaderTusChecksumAlgorithm, "md5,sha1,crc32") } w.WriteHeader(http.StatusNoContent) } diff --git a/internal/http/services/owncloud/ocdav/propfind/mocks/GatewayClient.go b/internal/http/services/owncloud/ocdav/propfind/mocks/GatewayClient.go new file mode 100644 index 00000000000..9d03625ddb1 --- /dev/null +++ b/internal/http/services/owncloud/ocdav/propfind/mocks/GatewayClient.go @@ -0,0 +1,2735 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package mocks + +import ( + appproviderv1beta1 "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1" + applicationsv1beta1 "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1" + + authregistryv1beta1 "github.com/cs3org/go-cs3apis/cs3/auth/registry/v1beta1" + + collaborationv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + + context "context" + + corev1beta1 "github.com/cs3org/go-cs3apis/cs3/ocm/core/v1beta1" + + gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + + groupv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" + + grpc "google.golang.org/grpc" + + invitev1beta1 "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1" + + linkv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" + + mock "github.com/stretchr/testify/mock" + + ocmv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" + + preferencesv1beta1 "github.com/cs3org/go-cs3apis/cs3/preferences/v1beta1" + + providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + + registryv1beta1 "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1" + + txv1beta1 "github.com/cs3org/go-cs3apis/cs3/tx/v1beta1" + + userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + + v1beta1 "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" +) + +// GatewayClient is an autogenerated mock type for the GatewayClient type +type GatewayClient struct { + mock.Mock +} + +// AcceptInvite provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) AcceptInvite(ctx context.Context, in *invitev1beta1.AcceptInviteRequest, opts ...grpc.CallOption) (*invitev1beta1.AcceptInviteResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *invitev1beta1.AcceptInviteResponse + if rf, ok := ret.Get(0).(func(context.Context, *invitev1beta1.AcceptInviteRequest, ...grpc.CallOption) *invitev1beta1.AcceptInviteResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*invitev1beta1.AcceptInviteResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *invitev1beta1.AcceptInviteRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// AddAppProvider provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) AddAppProvider(ctx context.Context, in *registryv1beta1.AddAppProviderRequest, opts ...grpc.CallOption) (*registryv1beta1.AddAppProviderResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *registryv1beta1.AddAppProviderResponse + if rf, ok := ret.Get(0).(func(context.Context, *registryv1beta1.AddAppProviderRequest, ...grpc.CallOption) *registryv1beta1.AddAppProviderResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*registryv1beta1.AddAppProviderResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *registryv1beta1.AddAppProviderRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Authenticate provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) Authenticate(ctx context.Context, in *gatewayv1beta1.AuthenticateRequest, opts ...grpc.CallOption) (*gatewayv1beta1.AuthenticateResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *gatewayv1beta1.AuthenticateResponse + if rf, ok := ret.Get(0).(func(context.Context, *gatewayv1beta1.AuthenticateRequest, ...grpc.CallOption) *gatewayv1beta1.AuthenticateResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gatewayv1beta1.AuthenticateResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *gatewayv1beta1.AuthenticateRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CancelTransfer provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) CancelTransfer(ctx context.Context, in *txv1beta1.CancelTransferRequest, opts ...grpc.CallOption) (*txv1beta1.CancelTransferResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *txv1beta1.CancelTransferResponse + if rf, ok := ret.Get(0).(func(context.Context, *txv1beta1.CancelTransferRequest, ...grpc.CallOption) *txv1beta1.CancelTransferResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*txv1beta1.CancelTransferResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *txv1beta1.CancelTransferRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateContainer provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) CreateContainer(ctx context.Context, in *providerv1beta1.CreateContainerRequest, opts ...grpc.CallOption) (*providerv1beta1.CreateContainerResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.CreateContainerResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.CreateContainerRequest, ...grpc.CallOption) *providerv1beta1.CreateContainerResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.CreateContainerResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.CreateContainerRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateHome provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) CreateHome(ctx context.Context, in *providerv1beta1.CreateHomeRequest, opts ...grpc.CallOption) (*providerv1beta1.CreateHomeResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.CreateHomeResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.CreateHomeRequest, ...grpc.CallOption) *providerv1beta1.CreateHomeResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.CreateHomeResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.CreateHomeRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateOCMCoreShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) CreateOCMCoreShare(ctx context.Context, in *corev1beta1.CreateOCMCoreShareRequest, opts ...grpc.CallOption) (*corev1beta1.CreateOCMCoreShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *corev1beta1.CreateOCMCoreShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *corev1beta1.CreateOCMCoreShareRequest, ...grpc.CallOption) *corev1beta1.CreateOCMCoreShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*corev1beta1.CreateOCMCoreShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *corev1beta1.CreateOCMCoreShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateOCMShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) CreateOCMShare(ctx context.Context, in *ocmv1beta1.CreateOCMShareRequest, opts ...grpc.CallOption) (*ocmv1beta1.CreateOCMShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *ocmv1beta1.CreateOCMShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *ocmv1beta1.CreateOCMShareRequest, ...grpc.CallOption) *ocmv1beta1.CreateOCMShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ocmv1beta1.CreateOCMShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *ocmv1beta1.CreateOCMShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreatePublicShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) CreatePublicShare(ctx context.Context, in *linkv1beta1.CreatePublicShareRequest, opts ...grpc.CallOption) (*linkv1beta1.CreatePublicShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *linkv1beta1.CreatePublicShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *linkv1beta1.CreatePublicShareRequest, ...grpc.CallOption) *linkv1beta1.CreatePublicShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*linkv1beta1.CreatePublicShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *linkv1beta1.CreatePublicShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) CreateShare(ctx context.Context, in *collaborationv1beta1.CreateShareRequest, opts ...grpc.CallOption) (*collaborationv1beta1.CreateShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *collaborationv1beta1.CreateShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.CreateShareRequest, ...grpc.CallOption) *collaborationv1beta1.CreateShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.CreateShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.CreateShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateStorageSpace provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) CreateStorageSpace(ctx context.Context, in *providerv1beta1.CreateStorageSpaceRequest, opts ...grpc.CallOption) (*providerv1beta1.CreateStorageSpaceResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.CreateStorageSpaceResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.CreateStorageSpaceRequest, ...grpc.CallOption) *providerv1beta1.CreateStorageSpaceResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.CreateStorageSpaceResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.CreateStorageSpaceRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateSymlink provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) CreateSymlink(ctx context.Context, in *providerv1beta1.CreateSymlinkRequest, opts ...grpc.CallOption) (*providerv1beta1.CreateSymlinkResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.CreateSymlinkResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.CreateSymlinkRequest, ...grpc.CallOption) *providerv1beta1.CreateSymlinkResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.CreateSymlinkResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.CreateSymlinkRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateTransfer provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) CreateTransfer(ctx context.Context, in *txv1beta1.CreateTransferRequest, opts ...grpc.CallOption) (*txv1beta1.CreateTransferResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *txv1beta1.CreateTransferResponse + if rf, ok := ret.Get(0).(func(context.Context, *txv1beta1.CreateTransferRequest, ...grpc.CallOption) *txv1beta1.CreateTransferResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*txv1beta1.CreateTransferResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *txv1beta1.CreateTransferRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Delete provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) Delete(ctx context.Context, in *providerv1beta1.DeleteRequest, opts ...grpc.CallOption) (*providerv1beta1.DeleteResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.DeleteResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.DeleteRequest, ...grpc.CallOption) *providerv1beta1.DeleteResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.DeleteResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.DeleteRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DeleteStorageSpace provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) DeleteStorageSpace(ctx context.Context, in *providerv1beta1.DeleteStorageSpaceRequest, opts ...grpc.CallOption) (*providerv1beta1.DeleteStorageSpaceResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.DeleteStorageSpaceResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.DeleteStorageSpaceRequest, ...grpc.CallOption) *providerv1beta1.DeleteStorageSpaceResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.DeleteStorageSpaceResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.DeleteStorageSpaceRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindAcceptedUsers provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) FindAcceptedUsers(ctx context.Context, in *invitev1beta1.FindAcceptedUsersRequest, opts ...grpc.CallOption) (*invitev1beta1.FindAcceptedUsersResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *invitev1beta1.FindAcceptedUsersResponse + if rf, ok := ret.Get(0).(func(context.Context, *invitev1beta1.FindAcceptedUsersRequest, ...grpc.CallOption) *invitev1beta1.FindAcceptedUsersResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*invitev1beta1.FindAcceptedUsersResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *invitev1beta1.FindAcceptedUsersRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindGroups provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) FindGroups(ctx context.Context, in *groupv1beta1.FindGroupsRequest, opts ...grpc.CallOption) (*groupv1beta1.FindGroupsResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *groupv1beta1.FindGroupsResponse + if rf, ok := ret.Get(0).(func(context.Context, *groupv1beta1.FindGroupsRequest, ...grpc.CallOption) *groupv1beta1.FindGroupsResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*groupv1beta1.FindGroupsResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *groupv1beta1.FindGroupsRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindUsers provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) FindUsers(ctx context.Context, in *userv1beta1.FindUsersRequest, opts ...grpc.CallOption) (*userv1beta1.FindUsersResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *userv1beta1.FindUsersResponse + if rf, ok := ret.Get(0).(func(context.Context, *userv1beta1.FindUsersRequest, ...grpc.CallOption) *userv1beta1.FindUsersResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*userv1beta1.FindUsersResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *userv1beta1.FindUsersRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ForwardInvite provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ForwardInvite(ctx context.Context, in *invitev1beta1.ForwardInviteRequest, opts ...grpc.CallOption) (*invitev1beta1.ForwardInviteResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *invitev1beta1.ForwardInviteResponse + if rf, ok := ret.Get(0).(func(context.Context, *invitev1beta1.ForwardInviteRequest, ...grpc.CallOption) *invitev1beta1.ForwardInviteResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*invitev1beta1.ForwardInviteResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *invitev1beta1.ForwardInviteRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GenerateAppPassword provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GenerateAppPassword(ctx context.Context, in *applicationsv1beta1.GenerateAppPasswordRequest, opts ...grpc.CallOption) (*applicationsv1beta1.GenerateAppPasswordResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *applicationsv1beta1.GenerateAppPasswordResponse + if rf, ok := ret.Get(0).(func(context.Context, *applicationsv1beta1.GenerateAppPasswordRequest, ...grpc.CallOption) *applicationsv1beta1.GenerateAppPasswordResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*applicationsv1beta1.GenerateAppPasswordResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *applicationsv1beta1.GenerateAppPasswordRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GenerateInviteToken provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GenerateInviteToken(ctx context.Context, in *invitev1beta1.GenerateInviteTokenRequest, opts ...grpc.CallOption) (*invitev1beta1.GenerateInviteTokenResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *invitev1beta1.GenerateInviteTokenResponse + if rf, ok := ret.Get(0).(func(context.Context, *invitev1beta1.GenerateInviteTokenRequest, ...grpc.CallOption) *invitev1beta1.GenerateInviteTokenResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*invitev1beta1.GenerateInviteTokenResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *invitev1beta1.GenerateInviteTokenRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetAcceptedUser provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetAcceptedUser(ctx context.Context, in *invitev1beta1.GetAcceptedUserRequest, opts ...grpc.CallOption) (*invitev1beta1.GetAcceptedUserResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *invitev1beta1.GetAcceptedUserResponse + if rf, ok := ret.Get(0).(func(context.Context, *invitev1beta1.GetAcceptedUserRequest, ...grpc.CallOption) *invitev1beta1.GetAcceptedUserResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*invitev1beta1.GetAcceptedUserResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *invitev1beta1.GetAcceptedUserRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetAppPassword provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetAppPassword(ctx context.Context, in *applicationsv1beta1.GetAppPasswordRequest, opts ...grpc.CallOption) (*applicationsv1beta1.GetAppPasswordResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *applicationsv1beta1.GetAppPasswordResponse + if rf, ok := ret.Get(0).(func(context.Context, *applicationsv1beta1.GetAppPasswordRequest, ...grpc.CallOption) *applicationsv1beta1.GetAppPasswordResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*applicationsv1beta1.GetAppPasswordResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *applicationsv1beta1.GetAppPasswordRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetAppProviders provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetAppProviders(ctx context.Context, in *registryv1beta1.GetAppProvidersRequest, opts ...grpc.CallOption) (*registryv1beta1.GetAppProvidersResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *registryv1beta1.GetAppProvidersResponse + if rf, ok := ret.Get(0).(func(context.Context, *registryv1beta1.GetAppProvidersRequest, ...grpc.CallOption) *registryv1beta1.GetAppProvidersResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*registryv1beta1.GetAppProvidersResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *registryv1beta1.GetAppProvidersRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetDefaultAppProviderForMimeType provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetDefaultAppProviderForMimeType(ctx context.Context, in *registryv1beta1.GetDefaultAppProviderForMimeTypeRequest, opts ...grpc.CallOption) (*registryv1beta1.GetDefaultAppProviderForMimeTypeResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *registryv1beta1.GetDefaultAppProviderForMimeTypeResponse + if rf, ok := ret.Get(0).(func(context.Context, *registryv1beta1.GetDefaultAppProviderForMimeTypeRequest, ...grpc.CallOption) *registryv1beta1.GetDefaultAppProviderForMimeTypeResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*registryv1beta1.GetDefaultAppProviderForMimeTypeResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *registryv1beta1.GetDefaultAppProviderForMimeTypeRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetGroup provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetGroup(ctx context.Context, in *groupv1beta1.GetGroupRequest, opts ...grpc.CallOption) (*groupv1beta1.GetGroupResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *groupv1beta1.GetGroupResponse + if rf, ok := ret.Get(0).(func(context.Context, *groupv1beta1.GetGroupRequest, ...grpc.CallOption) *groupv1beta1.GetGroupResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*groupv1beta1.GetGroupResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *groupv1beta1.GetGroupRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetGroupByClaim provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetGroupByClaim(ctx context.Context, in *groupv1beta1.GetGroupByClaimRequest, opts ...grpc.CallOption) (*groupv1beta1.GetGroupByClaimResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *groupv1beta1.GetGroupByClaimResponse + if rf, ok := ret.Get(0).(func(context.Context, *groupv1beta1.GetGroupByClaimRequest, ...grpc.CallOption) *groupv1beta1.GetGroupByClaimResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*groupv1beta1.GetGroupByClaimResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *groupv1beta1.GetGroupByClaimRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetHome provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetHome(ctx context.Context, in *providerv1beta1.GetHomeRequest, opts ...grpc.CallOption) (*providerv1beta1.GetHomeResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.GetHomeResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.GetHomeRequest, ...grpc.CallOption) *providerv1beta1.GetHomeResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.GetHomeResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.GetHomeRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetInfoByDomain provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetInfoByDomain(ctx context.Context, in *v1beta1.GetInfoByDomainRequest, opts ...grpc.CallOption) (*v1beta1.GetInfoByDomainResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *v1beta1.GetInfoByDomainResponse + if rf, ok := ret.Get(0).(func(context.Context, *v1beta1.GetInfoByDomainRequest, ...grpc.CallOption) *v1beta1.GetInfoByDomainResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1beta1.GetInfoByDomainResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *v1beta1.GetInfoByDomainRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetKey provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetKey(ctx context.Context, in *preferencesv1beta1.GetKeyRequest, opts ...grpc.CallOption) (*preferencesv1beta1.GetKeyResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *preferencesv1beta1.GetKeyResponse + if rf, ok := ret.Get(0).(func(context.Context, *preferencesv1beta1.GetKeyRequest, ...grpc.CallOption) *preferencesv1beta1.GetKeyResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*preferencesv1beta1.GetKeyResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *preferencesv1beta1.GetKeyRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetLock provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetLock(ctx context.Context, in *providerv1beta1.GetLockRequest, opts ...grpc.CallOption) (*providerv1beta1.GetLockResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.GetLockResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.GetLockRequest, ...grpc.CallOption) *providerv1beta1.GetLockResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.GetLockResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.GetLockRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetMembers provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetMembers(ctx context.Context, in *groupv1beta1.GetMembersRequest, opts ...grpc.CallOption) (*groupv1beta1.GetMembersResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *groupv1beta1.GetMembersResponse + if rf, ok := ret.Get(0).(func(context.Context, *groupv1beta1.GetMembersRequest, ...grpc.CallOption) *groupv1beta1.GetMembersResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*groupv1beta1.GetMembersResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *groupv1beta1.GetMembersRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetOCMShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetOCMShare(ctx context.Context, in *ocmv1beta1.GetOCMShareRequest, opts ...grpc.CallOption) (*ocmv1beta1.GetOCMShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *ocmv1beta1.GetOCMShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *ocmv1beta1.GetOCMShareRequest, ...grpc.CallOption) *ocmv1beta1.GetOCMShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ocmv1beta1.GetOCMShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *ocmv1beta1.GetOCMShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetPath provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetPath(ctx context.Context, in *providerv1beta1.GetPathRequest, opts ...grpc.CallOption) (*providerv1beta1.GetPathResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.GetPathResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.GetPathRequest, ...grpc.CallOption) *providerv1beta1.GetPathResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.GetPathResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.GetPathRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetPublicShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetPublicShare(ctx context.Context, in *linkv1beta1.GetPublicShareRequest, opts ...grpc.CallOption) (*linkv1beta1.GetPublicShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *linkv1beta1.GetPublicShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *linkv1beta1.GetPublicShareRequest, ...grpc.CallOption) *linkv1beta1.GetPublicShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*linkv1beta1.GetPublicShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *linkv1beta1.GetPublicShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetPublicShareByToken provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetPublicShareByToken(ctx context.Context, in *linkv1beta1.GetPublicShareByTokenRequest, opts ...grpc.CallOption) (*linkv1beta1.GetPublicShareByTokenResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *linkv1beta1.GetPublicShareByTokenResponse + if rf, ok := ret.Get(0).(func(context.Context, *linkv1beta1.GetPublicShareByTokenRequest, ...grpc.CallOption) *linkv1beta1.GetPublicShareByTokenResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*linkv1beta1.GetPublicShareByTokenResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *linkv1beta1.GetPublicShareByTokenRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetQuota provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetQuota(ctx context.Context, in *gatewayv1beta1.GetQuotaRequest, opts ...grpc.CallOption) (*providerv1beta1.GetQuotaResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.GetQuotaResponse + if rf, ok := ret.Get(0).(func(context.Context, *gatewayv1beta1.GetQuotaRequest, ...grpc.CallOption) *providerv1beta1.GetQuotaResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.GetQuotaResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *gatewayv1beta1.GetQuotaRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetReceivedOCMShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetReceivedOCMShare(ctx context.Context, in *ocmv1beta1.GetReceivedOCMShareRequest, opts ...grpc.CallOption) (*ocmv1beta1.GetReceivedOCMShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *ocmv1beta1.GetReceivedOCMShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *ocmv1beta1.GetReceivedOCMShareRequest, ...grpc.CallOption) *ocmv1beta1.GetReceivedOCMShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ocmv1beta1.GetReceivedOCMShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *ocmv1beta1.GetReceivedOCMShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetReceivedShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetReceivedShare(ctx context.Context, in *collaborationv1beta1.GetReceivedShareRequest, opts ...grpc.CallOption) (*collaborationv1beta1.GetReceivedShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *collaborationv1beta1.GetReceivedShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.GetReceivedShareRequest, ...grpc.CallOption) *collaborationv1beta1.GetReceivedShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.GetReceivedShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.GetReceivedShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetShare(ctx context.Context, in *collaborationv1beta1.GetShareRequest, opts ...grpc.CallOption) (*collaborationv1beta1.GetShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *collaborationv1beta1.GetShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.GetShareRequest, ...grpc.CallOption) *collaborationv1beta1.GetShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.GetShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.GetShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTransferStatus provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetTransferStatus(ctx context.Context, in *txv1beta1.GetTransferStatusRequest, opts ...grpc.CallOption) (*txv1beta1.GetTransferStatusResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *txv1beta1.GetTransferStatusResponse + if rf, ok := ret.Get(0).(func(context.Context, *txv1beta1.GetTransferStatusRequest, ...grpc.CallOption) *txv1beta1.GetTransferStatusResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*txv1beta1.GetTransferStatusResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *txv1beta1.GetTransferStatusRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetUser provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetUser(ctx context.Context, in *userv1beta1.GetUserRequest, opts ...grpc.CallOption) (*userv1beta1.GetUserResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *userv1beta1.GetUserResponse + if rf, ok := ret.Get(0).(func(context.Context, *userv1beta1.GetUserRequest, ...grpc.CallOption) *userv1beta1.GetUserResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*userv1beta1.GetUserResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *userv1beta1.GetUserRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetUserByClaim provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetUserByClaim(ctx context.Context, in *userv1beta1.GetUserByClaimRequest, opts ...grpc.CallOption) (*userv1beta1.GetUserByClaimResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *userv1beta1.GetUserByClaimResponse + if rf, ok := ret.Get(0).(func(context.Context, *userv1beta1.GetUserByClaimRequest, ...grpc.CallOption) *userv1beta1.GetUserByClaimResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*userv1beta1.GetUserByClaimResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *userv1beta1.GetUserByClaimRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetUserGroups provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetUserGroups(ctx context.Context, in *userv1beta1.GetUserGroupsRequest, opts ...grpc.CallOption) (*userv1beta1.GetUserGroupsResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *userv1beta1.GetUserGroupsResponse + if rf, ok := ret.Get(0).(func(context.Context, *userv1beta1.GetUserGroupsRequest, ...grpc.CallOption) *userv1beta1.GetUserGroupsResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*userv1beta1.GetUserGroupsResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *userv1beta1.GetUserGroupsRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// HasMember provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) HasMember(ctx context.Context, in *groupv1beta1.HasMemberRequest, opts ...grpc.CallOption) (*groupv1beta1.HasMemberResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *groupv1beta1.HasMemberResponse + if rf, ok := ret.Get(0).(func(context.Context, *groupv1beta1.HasMemberRequest, ...grpc.CallOption) *groupv1beta1.HasMemberResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*groupv1beta1.HasMemberResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *groupv1beta1.HasMemberRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// InitiateFileDownload provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) InitiateFileDownload(ctx context.Context, in *providerv1beta1.InitiateFileDownloadRequest, opts ...grpc.CallOption) (*gatewayv1beta1.InitiateFileDownloadResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *gatewayv1beta1.InitiateFileDownloadResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.InitiateFileDownloadRequest, ...grpc.CallOption) *gatewayv1beta1.InitiateFileDownloadResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gatewayv1beta1.InitiateFileDownloadResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.InitiateFileDownloadRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// InitiateFileUpload provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) InitiateFileUpload(ctx context.Context, in *providerv1beta1.InitiateFileUploadRequest, opts ...grpc.CallOption) (*gatewayv1beta1.InitiateFileUploadResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *gatewayv1beta1.InitiateFileUploadResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.InitiateFileUploadRequest, ...grpc.CallOption) *gatewayv1beta1.InitiateFileUploadResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gatewayv1beta1.InitiateFileUploadResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.InitiateFileUploadRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// InvalidateAppPassword provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) InvalidateAppPassword(ctx context.Context, in *applicationsv1beta1.InvalidateAppPasswordRequest, opts ...grpc.CallOption) (*applicationsv1beta1.InvalidateAppPasswordResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *applicationsv1beta1.InvalidateAppPasswordResponse + if rf, ok := ret.Get(0).(func(context.Context, *applicationsv1beta1.InvalidateAppPasswordRequest, ...grpc.CallOption) *applicationsv1beta1.InvalidateAppPasswordResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*applicationsv1beta1.InvalidateAppPasswordResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *applicationsv1beta1.InvalidateAppPasswordRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// IsProviderAllowed provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) IsProviderAllowed(ctx context.Context, in *v1beta1.IsProviderAllowedRequest, opts ...grpc.CallOption) (*v1beta1.IsProviderAllowedResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *v1beta1.IsProviderAllowedResponse + if rf, ok := ret.Get(0).(func(context.Context, *v1beta1.IsProviderAllowedRequest, ...grpc.CallOption) *v1beta1.IsProviderAllowedResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1beta1.IsProviderAllowedResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *v1beta1.IsProviderAllowedRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListAllProviders provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListAllProviders(ctx context.Context, in *v1beta1.ListAllProvidersRequest, opts ...grpc.CallOption) (*v1beta1.ListAllProvidersResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *v1beta1.ListAllProvidersResponse + if rf, ok := ret.Get(0).(func(context.Context, *v1beta1.ListAllProvidersRequest, ...grpc.CallOption) *v1beta1.ListAllProvidersResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1beta1.ListAllProvidersResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *v1beta1.ListAllProvidersRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListAppPasswords provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListAppPasswords(ctx context.Context, in *applicationsv1beta1.ListAppPasswordsRequest, opts ...grpc.CallOption) (*applicationsv1beta1.ListAppPasswordsResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *applicationsv1beta1.ListAppPasswordsResponse + if rf, ok := ret.Get(0).(func(context.Context, *applicationsv1beta1.ListAppPasswordsRequest, ...grpc.CallOption) *applicationsv1beta1.ListAppPasswordsResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*applicationsv1beta1.ListAppPasswordsResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *applicationsv1beta1.ListAppPasswordsRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListAppProviders provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListAppProviders(ctx context.Context, in *registryv1beta1.ListAppProvidersRequest, opts ...grpc.CallOption) (*registryv1beta1.ListAppProvidersResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *registryv1beta1.ListAppProvidersResponse + if rf, ok := ret.Get(0).(func(context.Context, *registryv1beta1.ListAppProvidersRequest, ...grpc.CallOption) *registryv1beta1.ListAppProvidersResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*registryv1beta1.ListAppProvidersResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *registryv1beta1.ListAppProvidersRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListAuthProviders provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListAuthProviders(ctx context.Context, in *authregistryv1beta1.ListAuthProvidersRequest, opts ...grpc.CallOption) (*gatewayv1beta1.ListAuthProvidersResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *gatewayv1beta1.ListAuthProvidersResponse + if rf, ok := ret.Get(0).(func(context.Context, *authregistryv1beta1.ListAuthProvidersRequest, ...grpc.CallOption) *gatewayv1beta1.ListAuthProvidersResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gatewayv1beta1.ListAuthProvidersResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *authregistryv1beta1.ListAuthProvidersRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListContainer provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListContainer(ctx context.Context, in *providerv1beta1.ListContainerRequest, opts ...grpc.CallOption) (*providerv1beta1.ListContainerResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.ListContainerResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ListContainerRequest, ...grpc.CallOption) *providerv1beta1.ListContainerResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.ListContainerResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ListContainerRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListContainerStream provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListContainerStream(ctx context.Context, in *providerv1beta1.ListContainerStreamRequest, opts ...grpc.CallOption) (gatewayv1beta1.GatewayAPI_ListContainerStreamClient, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 gatewayv1beta1.GatewayAPI_ListContainerStreamClient + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ListContainerStreamRequest, ...grpc.CallOption) gatewayv1beta1.GatewayAPI_ListContainerStreamClient); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(gatewayv1beta1.GatewayAPI_ListContainerStreamClient) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ListContainerStreamRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListFileVersions provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListFileVersions(ctx context.Context, in *providerv1beta1.ListFileVersionsRequest, opts ...grpc.CallOption) (*providerv1beta1.ListFileVersionsResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.ListFileVersionsResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ListFileVersionsRequest, ...grpc.CallOption) *providerv1beta1.ListFileVersionsResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.ListFileVersionsResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ListFileVersionsRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListOCMShares provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListOCMShares(ctx context.Context, in *ocmv1beta1.ListOCMSharesRequest, opts ...grpc.CallOption) (*ocmv1beta1.ListOCMSharesResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *ocmv1beta1.ListOCMSharesResponse + if rf, ok := ret.Get(0).(func(context.Context, *ocmv1beta1.ListOCMSharesRequest, ...grpc.CallOption) *ocmv1beta1.ListOCMSharesResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ocmv1beta1.ListOCMSharesResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *ocmv1beta1.ListOCMSharesRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListPublicShares provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListPublicShares(ctx context.Context, in *linkv1beta1.ListPublicSharesRequest, opts ...grpc.CallOption) (*linkv1beta1.ListPublicSharesResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *linkv1beta1.ListPublicSharesResponse + if rf, ok := ret.Get(0).(func(context.Context, *linkv1beta1.ListPublicSharesRequest, ...grpc.CallOption) *linkv1beta1.ListPublicSharesResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*linkv1beta1.ListPublicSharesResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *linkv1beta1.ListPublicSharesRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListReceivedOCMShares provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListReceivedOCMShares(ctx context.Context, in *ocmv1beta1.ListReceivedOCMSharesRequest, opts ...grpc.CallOption) (*ocmv1beta1.ListReceivedOCMSharesResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *ocmv1beta1.ListReceivedOCMSharesResponse + if rf, ok := ret.Get(0).(func(context.Context, *ocmv1beta1.ListReceivedOCMSharesRequest, ...grpc.CallOption) *ocmv1beta1.ListReceivedOCMSharesResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ocmv1beta1.ListReceivedOCMSharesResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *ocmv1beta1.ListReceivedOCMSharesRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListReceivedShares provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListReceivedShares(ctx context.Context, in *collaborationv1beta1.ListReceivedSharesRequest, opts ...grpc.CallOption) (*collaborationv1beta1.ListReceivedSharesResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *collaborationv1beta1.ListReceivedSharesResponse + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.ListReceivedSharesRequest, ...grpc.CallOption) *collaborationv1beta1.ListReceivedSharesResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.ListReceivedSharesResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.ListReceivedSharesRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListRecycle provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListRecycle(ctx context.Context, in *providerv1beta1.ListRecycleRequest, opts ...grpc.CallOption) (*providerv1beta1.ListRecycleResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.ListRecycleResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ListRecycleRequest, ...grpc.CallOption) *providerv1beta1.ListRecycleResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.ListRecycleResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ListRecycleRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListRecycleStream provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListRecycleStream(ctx context.Context, in *providerv1beta1.ListRecycleStreamRequest, opts ...grpc.CallOption) (gatewayv1beta1.GatewayAPI_ListRecycleStreamClient, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 gatewayv1beta1.GatewayAPI_ListRecycleStreamClient + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ListRecycleStreamRequest, ...grpc.CallOption) gatewayv1beta1.GatewayAPI_ListRecycleStreamClient); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(gatewayv1beta1.GatewayAPI_ListRecycleStreamClient) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ListRecycleStreamRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListShares provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListShares(ctx context.Context, in *collaborationv1beta1.ListSharesRequest, opts ...grpc.CallOption) (*collaborationv1beta1.ListSharesResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *collaborationv1beta1.ListSharesResponse + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.ListSharesRequest, ...grpc.CallOption) *collaborationv1beta1.ListSharesResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.ListSharesResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.ListSharesRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListStorageSpaces provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListStorageSpaces(ctx context.Context, in *providerv1beta1.ListStorageSpacesRequest, opts ...grpc.CallOption) (*providerv1beta1.ListStorageSpacesResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.ListStorageSpacesResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ListStorageSpacesRequest, ...grpc.CallOption) *providerv1beta1.ListStorageSpacesResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.ListStorageSpacesResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ListStorageSpacesRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListSupportedMimeTypes provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) ListSupportedMimeTypes(ctx context.Context, in *registryv1beta1.ListSupportedMimeTypesRequest, opts ...grpc.CallOption) (*registryv1beta1.ListSupportedMimeTypesResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *registryv1beta1.ListSupportedMimeTypesResponse + if rf, ok := ret.Get(0).(func(context.Context, *registryv1beta1.ListSupportedMimeTypesRequest, ...grpc.CallOption) *registryv1beta1.ListSupportedMimeTypesResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*registryv1beta1.ListSupportedMimeTypesResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *registryv1beta1.ListSupportedMimeTypesRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Move provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) Move(ctx context.Context, in *providerv1beta1.MoveRequest, opts ...grpc.CallOption) (*providerv1beta1.MoveResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.MoveResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.MoveRequest, ...grpc.CallOption) *providerv1beta1.MoveResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.MoveResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.MoveRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// OpenInApp provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) OpenInApp(ctx context.Context, in *gatewayv1beta1.OpenInAppRequest, opts ...grpc.CallOption) (*appproviderv1beta1.OpenInAppResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *appproviderv1beta1.OpenInAppResponse + if rf, ok := ret.Get(0).(func(context.Context, *gatewayv1beta1.OpenInAppRequest, ...grpc.CallOption) *appproviderv1beta1.OpenInAppResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*appproviderv1beta1.OpenInAppResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *gatewayv1beta1.OpenInAppRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// PurgeRecycle provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) PurgeRecycle(ctx context.Context, in *providerv1beta1.PurgeRecycleRequest, opts ...grpc.CallOption) (*providerv1beta1.PurgeRecycleResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.PurgeRecycleResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.PurgeRecycleRequest, ...grpc.CallOption) *providerv1beta1.PurgeRecycleResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.PurgeRecycleResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.PurgeRecycleRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RefreshLock provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) RefreshLock(ctx context.Context, in *providerv1beta1.RefreshLockRequest, opts ...grpc.CallOption) (*providerv1beta1.RefreshLockResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.RefreshLockResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.RefreshLockRequest, ...grpc.CallOption) *providerv1beta1.RefreshLockResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.RefreshLockResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.RefreshLockRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoveOCMShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) RemoveOCMShare(ctx context.Context, in *ocmv1beta1.RemoveOCMShareRequest, opts ...grpc.CallOption) (*ocmv1beta1.RemoveOCMShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *ocmv1beta1.RemoveOCMShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *ocmv1beta1.RemoveOCMShareRequest, ...grpc.CallOption) *ocmv1beta1.RemoveOCMShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ocmv1beta1.RemoveOCMShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *ocmv1beta1.RemoveOCMShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemovePublicShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) RemovePublicShare(ctx context.Context, in *linkv1beta1.RemovePublicShareRequest, opts ...grpc.CallOption) (*linkv1beta1.RemovePublicShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *linkv1beta1.RemovePublicShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *linkv1beta1.RemovePublicShareRequest, ...grpc.CallOption) *linkv1beta1.RemovePublicShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*linkv1beta1.RemovePublicShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *linkv1beta1.RemovePublicShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoveShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) RemoveShare(ctx context.Context, in *collaborationv1beta1.RemoveShareRequest, opts ...grpc.CallOption) (*collaborationv1beta1.RemoveShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *collaborationv1beta1.RemoveShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.RemoveShareRequest, ...grpc.CallOption) *collaborationv1beta1.RemoveShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.RemoveShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.RemoveShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RestoreFileVersion provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) RestoreFileVersion(ctx context.Context, in *providerv1beta1.RestoreFileVersionRequest, opts ...grpc.CallOption) (*providerv1beta1.RestoreFileVersionResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.RestoreFileVersionResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.RestoreFileVersionRequest, ...grpc.CallOption) *providerv1beta1.RestoreFileVersionResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.RestoreFileVersionResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.RestoreFileVersionRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RestoreRecycleItem provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) RestoreRecycleItem(ctx context.Context, in *providerv1beta1.RestoreRecycleItemRequest, opts ...grpc.CallOption) (*providerv1beta1.RestoreRecycleItemResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.RestoreRecycleItemResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.RestoreRecycleItemRequest, ...grpc.CallOption) *providerv1beta1.RestoreRecycleItemResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.RestoreRecycleItemResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.RestoreRecycleItemRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SetArbitraryMetadata provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) SetArbitraryMetadata(ctx context.Context, in *providerv1beta1.SetArbitraryMetadataRequest, opts ...grpc.CallOption) (*providerv1beta1.SetArbitraryMetadataResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.SetArbitraryMetadataResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.SetArbitraryMetadataRequest, ...grpc.CallOption) *providerv1beta1.SetArbitraryMetadataResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.SetArbitraryMetadataResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.SetArbitraryMetadataRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SetDefaultAppProviderForMimeType provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) SetDefaultAppProviderForMimeType(ctx context.Context, in *registryv1beta1.SetDefaultAppProviderForMimeTypeRequest, opts ...grpc.CallOption) (*registryv1beta1.SetDefaultAppProviderForMimeTypeResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *registryv1beta1.SetDefaultAppProviderForMimeTypeResponse + if rf, ok := ret.Get(0).(func(context.Context, *registryv1beta1.SetDefaultAppProviderForMimeTypeRequest, ...grpc.CallOption) *registryv1beta1.SetDefaultAppProviderForMimeTypeResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*registryv1beta1.SetDefaultAppProviderForMimeTypeResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *registryv1beta1.SetDefaultAppProviderForMimeTypeRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SetKey provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) SetKey(ctx context.Context, in *preferencesv1beta1.SetKeyRequest, opts ...grpc.CallOption) (*preferencesv1beta1.SetKeyResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *preferencesv1beta1.SetKeyResponse + if rf, ok := ret.Get(0).(func(context.Context, *preferencesv1beta1.SetKeyRequest, ...grpc.CallOption) *preferencesv1beta1.SetKeyResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*preferencesv1beta1.SetKeyResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *preferencesv1beta1.SetKeyRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SetLock provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) SetLock(ctx context.Context, in *providerv1beta1.SetLockRequest, opts ...grpc.CallOption) (*providerv1beta1.SetLockResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.SetLockResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.SetLockRequest, ...grpc.CallOption) *providerv1beta1.SetLockResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.SetLockResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.SetLockRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Stat provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) Stat(ctx context.Context, in *providerv1beta1.StatRequest, opts ...grpc.CallOption) (*providerv1beta1.StatResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.StatResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.StatRequest, ...grpc.CallOption) *providerv1beta1.StatResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.StatResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.StatRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// TouchFile provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) TouchFile(ctx context.Context, in *providerv1beta1.TouchFileRequest, opts ...grpc.CallOption) (*providerv1beta1.TouchFileResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.TouchFileResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.TouchFileRequest, ...grpc.CallOption) *providerv1beta1.TouchFileResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.TouchFileResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.TouchFileRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Unlock provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) Unlock(ctx context.Context, in *providerv1beta1.UnlockRequest, opts ...grpc.CallOption) (*providerv1beta1.UnlockResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.UnlockResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.UnlockRequest, ...grpc.CallOption) *providerv1beta1.UnlockResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.UnlockResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.UnlockRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UnsetArbitraryMetadata provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) UnsetArbitraryMetadata(ctx context.Context, in *providerv1beta1.UnsetArbitraryMetadataRequest, opts ...grpc.CallOption) (*providerv1beta1.UnsetArbitraryMetadataResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.UnsetArbitraryMetadataResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.UnsetArbitraryMetadataRequest, ...grpc.CallOption) *providerv1beta1.UnsetArbitraryMetadataResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.UnsetArbitraryMetadataResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.UnsetArbitraryMetadataRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateOCMShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) UpdateOCMShare(ctx context.Context, in *ocmv1beta1.UpdateOCMShareRequest, opts ...grpc.CallOption) (*ocmv1beta1.UpdateOCMShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *ocmv1beta1.UpdateOCMShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *ocmv1beta1.UpdateOCMShareRequest, ...grpc.CallOption) *ocmv1beta1.UpdateOCMShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ocmv1beta1.UpdateOCMShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *ocmv1beta1.UpdateOCMShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdatePublicShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) UpdatePublicShare(ctx context.Context, in *linkv1beta1.UpdatePublicShareRequest, opts ...grpc.CallOption) (*linkv1beta1.UpdatePublicShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *linkv1beta1.UpdatePublicShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *linkv1beta1.UpdatePublicShareRequest, ...grpc.CallOption) *linkv1beta1.UpdatePublicShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*linkv1beta1.UpdatePublicShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *linkv1beta1.UpdatePublicShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateReceivedOCMShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) UpdateReceivedOCMShare(ctx context.Context, in *ocmv1beta1.UpdateReceivedOCMShareRequest, opts ...grpc.CallOption) (*ocmv1beta1.UpdateReceivedOCMShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *ocmv1beta1.UpdateReceivedOCMShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *ocmv1beta1.UpdateReceivedOCMShareRequest, ...grpc.CallOption) *ocmv1beta1.UpdateReceivedOCMShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ocmv1beta1.UpdateReceivedOCMShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *ocmv1beta1.UpdateReceivedOCMShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateReceivedShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) UpdateReceivedShare(ctx context.Context, in *collaborationv1beta1.UpdateReceivedShareRequest, opts ...grpc.CallOption) (*collaborationv1beta1.UpdateReceivedShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *collaborationv1beta1.UpdateReceivedShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.UpdateReceivedShareRequest, ...grpc.CallOption) *collaborationv1beta1.UpdateReceivedShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.UpdateReceivedShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.UpdateReceivedShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) UpdateShare(ctx context.Context, in *collaborationv1beta1.UpdateShareRequest, opts ...grpc.CallOption) (*collaborationv1beta1.UpdateShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *collaborationv1beta1.UpdateShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.UpdateShareRequest, ...grpc.CallOption) *collaborationv1beta1.UpdateShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.UpdateShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.UpdateShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateStorageSpace provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) UpdateStorageSpace(ctx context.Context, in *providerv1beta1.UpdateStorageSpaceRequest, opts ...grpc.CallOption) (*providerv1beta1.UpdateStorageSpaceResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.UpdateStorageSpaceResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.UpdateStorageSpaceRequest, ...grpc.CallOption) *providerv1beta1.UpdateStorageSpaceResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.UpdateStorageSpaceResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.UpdateStorageSpaceRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// WhoAmI provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) WhoAmI(ctx context.Context, in *gatewayv1beta1.WhoAmIRequest, opts ...grpc.CallOption) (*gatewayv1beta1.WhoAmIResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *gatewayv1beta1.WhoAmIResponse + if rf, ok := ret.Get(0).(func(context.Context, *gatewayv1beta1.WhoAmIRequest, ...grpc.CallOption) *gatewayv1beta1.WhoAmIResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gatewayv1beta1.WhoAmIResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *gatewayv1beta1.WhoAmIRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind/propfind.go similarity index 68% rename from internal/http/services/owncloud/ocdav/propfind.go rename to internal/http/services/owncloud/ocdav/propfind/propfind.go index 9732e06ca73..7d5a42c7b31 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind/propfind.go @@ -16,10 +16,9 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -package ocdav +package propfind import ( - "bytes" "context" "encoding/json" "encoding/xml" @@ -34,11 +33,14 @@ import ( "time" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/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" "github.com/cs3org/reva/internal/grpc/services/storageprovider" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/props" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" "github.com/cs3org/reva/pkg/appctx" ctxpkg "github.com/cs3org/reva/pkg/ctx" @@ -52,23 +54,33 @@ import ( "go.opentelemetry.io/otel/codes" ) -const ( - _nsDav = "DAV:" - _nsOwncloud = "http://owncloud.org/ns" - _nsOCS = "http://open-collaboration-services.org/ns" +//go:generate mockery -name GatewayClient - _propOcFavorite = "http://owncloud.org/ns/favorite" +// GatewayClient is the interface that's being uses to interact with the gateway +type GatewayClient interface { + gateway.GatewayAPIClient +} - // RFC1123 time that mimics oc10. time.RFC1123 would end in "UTC", see https://github.com/golang/go/issues/13781 - RFC1123 = "Mon, 02 Jan 2006 15:04:05 GMT" +// GetGatewayServiceClientFunc is a callback used to pass in a StorageProviderClient during testing +type GetGatewayServiceClientFunc func() (GatewayClient, error) - // _propQuotaUncalculated = "-1" - _propQuotaUnknown = "-2" - // _propQuotaUnlimited = "-3" -) +// Handler handles propfind requests +type Handler struct { + PublicURL string + getClient GetGatewayServiceClientFunc +} + +// NewHandler returns a new PropfindHandler instance +func NewHandler(publicURL string, getClientFunc GetGatewayServiceClientFunc) *Handler { + return &Handler{ + PublicURL: publicURL, + getClient: getClientFunc, + } +} +// HandlePathPropfind handles a path based propfind request // ns is the namespace that is prefixed to the path in the cs3 namespace -func (s *svc) handlePathPropfind(w http.ResponseWriter, r *http.Request, ns string) { +func (p *Handler) HandlePathPropfind(w http.ResponseWriter, r *http.Request, ns string) { ctx, span := rtrace.Provider.Tracer("reva").Start(r.Context(), fmt.Sprintf("%s %v", r.Method, r.URL.Path)) defer span.End() @@ -78,7 +90,7 @@ func (s *svc) handlePathPropfind(w http.ResponseWriter, r *http.Request, ns stri sublog := appctx.GetLogger(ctx).With().Str("path", fn).Logger() - pf, status, err := readPropfind(r.Body) + pf, status, err := ReadPropfind(r.Body) if err != nil { sublog.Debug().Err(err).Msg("error reading propfind request") w.WriteHeader(status) @@ -86,7 +98,14 @@ func (s *svc) handlePathPropfind(w http.ResponseWriter, r *http.Request, ns stri } // retrieve a specific storage space - spaces, rpcStatus, err := s.lookUpStorageSpacesForPathWithChildren(ctx, fn) + client, err := p.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error retrieving a gateway service client") + w.WriteHeader(http.StatusInternalServerError) + return + } + + spaces, rpcStatus, err := spacelookup.LookUpStorageSpacesForPathWithChildren(ctx, client.(gateway.GatewayAPIClient), fn) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -94,25 +113,32 @@ func (s *svc) handlePathPropfind(w http.ResponseWriter, r *http.Request, ns stri } if rpcStatus.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, rpcStatus) + errors.HandleErrorStatus(&sublog, w, rpcStatus) return } - resourceInfos, sendTusHeaders, ok := s.getResourceInfos(ctx, w, r, pf, spaces, fn, false, sublog) + resourceInfos, sendTusHeaders, ok := p.getResourceInfos(ctx, w, r, pf, spaces, fn, false, sublog) if !ok { // getResourceInfos handles responses in case of an error so we can just return here. return } - s.propfindResponse(ctx, w, r, ns, pf, sendTusHeaders, resourceInfos, sublog) + p.propfindResponse(ctx, w, r, ns, pf, sendTusHeaders, resourceInfos, sublog) } -func (s *svc) handleSpacesPropfind(w http.ResponseWriter, r *http.Request, spaceID string) { +// HandleSpacesPropfind handles a spaces based propfind request +func (p *Handler) HandleSpacesPropfind(w http.ResponseWriter, r *http.Request, spaceID string) { ctx, span := rtrace.Provider.Tracer("ocdav").Start(r.Context(), "spaces_propfind") defer span.End() sublog := appctx.GetLogger(ctx).With().Str("path", r.URL.Path).Str("spaceid", spaceID).Logger() + client, err := p.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } - pf, status, err := readPropfind(r.Body) + pf, status, err := ReadPropfind(r.Body) if err != nil { sublog.Debug().Err(err).Msg("error reading propfind request") w.WriteHeader(status) @@ -120,19 +146,19 @@ func (s *svc) handleSpacesPropfind(w http.ResponseWriter, r *http.Request, space } // retrieve a specific storage space - space, rpcStatus, err := s.lookUpStorageSpaceByID(ctx, spaceID) + space, rpcStatus, err := spacelookup.LookUpStorageSpaceByID(ctx, client.(gateway.GatewayAPIClient), spaceID) if err != nil { - sublog.Error().Err(err).Msg("error sending a grpc request") + sublog.Error().Err(err).Msg("error looking up the space by id") w.WriteHeader(http.StatusInternalServerError) return } if rpcStatus.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, rpcStatus) + errors.HandleErrorStatus(&sublog, w, rpcStatus) return } - resourceInfos, sendTusHeaders, ok := s.getResourceInfos(ctx, w, r, pf, []*provider.StorageSpace{space}, r.URL.Path, true, sublog) + resourceInfos, sendTusHeaders, ok := p.getResourceInfos(ctx, w, r, pf, []*provider.StorageSpace{space}, r.URL.Path, true, sublog) if !ok { // getResourceInfos handles responses in case of an error so we can just return here. return @@ -143,11 +169,11 @@ func (s *svc) handleSpacesPropfind(w http.ResponseWriter, r *http.Request, space resourceInfos[i].Path = path.Join("/", spaceID, resourceInfos[i].Path) } - s.propfindResponse(ctx, w, r, "", pf, sendTusHeaders, resourceInfos, sublog) + p.propfindResponse(ctx, w, r, "", pf, sendTusHeaders, resourceInfos, sublog) } -func (s *svc) propfindResponse(ctx context.Context, w http.ResponseWriter, r *http.Request, namespace string, pf propfindXML, sendTusHeaders bool, resourceInfos []*provider.ResourceInfo, log zerolog.Logger) { +func (p *Handler) propfindResponse(ctx context.Context, w http.ResponseWriter, r *http.Request, namespace string, pf XML, sendTusHeaders bool, resourceInfos []*provider.ResourceInfo, log zerolog.Logger) { ctx, span := rtrace.Provider.Tracer("ocdav").Start(ctx, "propfind_response") defer span.End() @@ -156,7 +182,7 @@ func (s *svc) propfindResponse(ctx context.Context, w http.ResponseWriter, r *ht filters = append(filters, publicshare.ResourceIDFilter(resourceInfos[i].Id)) } - client, err := s.getClient() + client, err := p.getClient() if err != nil { log.Error().Err(err).Msg("error getting grpc client") w.WriteHeader(http.StatusInternalServerError) @@ -175,20 +201,20 @@ func (s *svc) propfindResponse(ctx context.Context, w http.ResponseWriter, r *ht span.SetStatus(codes.Error, err.Error()) } - propRes, err := s.multistatusResponse(ctx, &pf, resourceInfos, namespace, linkshares) + propRes, err := MultistatusResponse(ctx, &pf, resourceInfos, p.PublicURL, namespace, linkshares) if err != nil { log.Error().Err(err).Msg("error formatting propfind") w.WriteHeader(http.StatusInternalServerError) return } - w.Header().Set(HeaderDav, "1, 3, extended-mkcol") - w.Header().Set(HeaderContentType, "application/xml; charset=utf-8") + w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol") + w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8") if sendTusHeaders { - w.Header().Add(HeaderAccessControlExposeHeaders, strings.Join([]string{HeaderTusResumable, HeaderTusVersion, HeaderTusExtension}, ", ")) - w.Header().Set(HeaderTusResumable, "1.0.0") - w.Header().Set(HeaderTusVersion, "1.0.0") - w.Header().Set(HeaderTusExtension, "creation,creation-with-upload,checksum,expiration") + w.Header().Add(net.HeaderAccessControlExposeHeaders, strings.Join([]string{net.HeaderTusResumable, net.HeaderTusVersion, net.HeaderTusExtension}, ", ")) + w.Header().Set(net.HeaderTusResumable, "1.0.0") + w.Header().Set(net.HeaderTusVersion, "1.0.0") + w.Header().Set(net.HeaderTusExtension, "creation,creation-with-upload,checksum,expiration") } w.WriteHeader(http.StatusMultiStatus) @@ -198,7 +224,7 @@ func (s *svc) propfindResponse(ctx context.Context, w http.ResponseWriter, r *ht } // TODO this is just a stat -> rename -func (s *svc) statSpace(ctx context.Context, client gateway.GatewayAPIClient, space *provider.StorageSpace, ref *provider.Reference, metadataKeys []string) (*provider.ResourceInfo, *rpc.Status, error) { +func (p *Handler) statSpace(ctx context.Context, client gateway.GatewayAPIClient, space *provider.StorageSpace, ref *provider.Reference, metadataKeys []string) (*provider.ResourceInfo, *rpc.Status, error) { req := &provider.StatRequest{ Ref: ref, ArbitraryMetadataKeys: metadataKeys, @@ -210,8 +236,8 @@ func (s *svc) statSpace(ctx context.Context, client gateway.GatewayAPIClient, sp return res.Info, res.Status, nil } -func (s *svc) getResourceInfos(ctx context.Context, w http.ResponseWriter, r *http.Request, pf propfindXML, spaces []*provider.StorageSpace, requestPath string, spacesPropfind bool, log zerolog.Logger) ([]*provider.ResourceInfo, bool, bool) { - depth := r.Header.Get(HeaderDepth) +func (p *Handler) getResourceInfos(ctx context.Context, w http.ResponseWriter, r *http.Request, pf XML, spaces []*provider.StorageSpace, requestPath string, spacesPropfind bool, log zerolog.Logger) ([]*provider.ResourceInfo, bool, bool) { + depth := r.Header.Get(net.HeaderDepth) if depth == "" { depth = "1" } @@ -220,15 +246,12 @@ func (s *svc) getResourceInfos(ctx context.Context, w http.ResponseWriter, r *ht log.Debug().Str("depth", depth).Msg("invalid Depth header value") w.WriteHeader(http.StatusBadRequest) m := fmt.Sprintf("Invalid Depth header value: %v", depth) - b, err := Marshal(exception{ - code: SabredavBadRequest, - message: m, - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavBadRequest, m, "") + errors.HandleWebdavError(&log, w, b, err) return nil, false, false } - client, err := s.getClient() + client, err := p.getClient() if err != nil { log.Error().Err(err).Msg("error getting grpc client") w.WriteHeader(http.StatusInternalServerError) @@ -266,8 +289,8 @@ func (s *svc) getResourceInfos(ctx context.Context, w http.ResponseWriter, r *ht spacePath := string(space.Opaque.Map["path"].Value) // TODO separate stats to the path or to the children, after statting all children update the mtime/etag // TODO get mtime, and size from space as well, so we no longer have to stat here? - spaceRef := makeRelativeReference(space, requestPath, spacesPropfind) - info, status, err := s.statSpace(ctx, client, space, spaceRef, metadataKeys) + spaceRef := spacelookup.MakeRelativeReference(space, requestPath, spacesPropfind) + info, status, err := p.statSpace(ctx, client.(gateway.GatewayAPIClient), space, spaceRef, metadataKeys) if err != nil || status.Code != rpc.Code_CODE_OK { continue } @@ -305,11 +328,8 @@ func (s *svc) getResourceInfos(ctx context.Context, w http.ResponseWriter, r *ht // TODO if we have children invent node on the fly w.WriteHeader(http.StatusNotFound) m := fmt.Sprintf("Resource %v not found", requestPath) - b, err := Marshal(exception{ - code: SabredavNotFound, - message: m, - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavNotFound, m, "") + errors.HandleWebdavError(&log, w, b, err) return nil, false, false } if mostRecentChildInfo != nil { @@ -471,7 +491,7 @@ func (s *svc) getResourceInfos(ctx context.Context, w http.ResponseWriter, r *ht func requiresExplicitFetching(n *xml.Name) bool { switch n.Space { - case _nsDav: + case net.NsDav: switch n.Local { case "quota-available-bytes", "quota-used-bytes": // A PROPFIND request SHOULD NOT return DAV:quota-available-bytes and DAV:quota-used-bytes @@ -480,54 +500,56 @@ func requiresExplicitFetching(n *xml.Name) bool { default: return false } - case _nsOwncloud: + case net.NsOwncloud: switch n.Local { case "favorite", "share-types", "checksums", "size": return true default: return false } - case _nsOCS: + case net.NsOCS: return false } return true } +// ReadPropfind extracts and parses the propfind XML information from a Reader // from https://github.com/golang/net/blob/e514e69ffb8bc3c76a71ae40de0118d794855992/webdav/xml.go#L178-L205 -func readPropfind(r io.Reader) (pf propfindXML, status int, err error) { +func ReadPropfind(r io.Reader) (pf XML, status int, err error) { c := countingReader{r: r} if err = xml.NewDecoder(&c).Decode(&pf); err != nil { if err == io.EOF { if c.n == 0 { // An empty body means to propfind allprop. // http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND - return propfindXML{Allprop: new(struct{})}, 0, nil + return XML{Allprop: new(struct{})}, 0, nil } - err = errInvalidPropfind + err = errors.ErrorInvalidPropfind } - return propfindXML{}, http.StatusBadRequest, err + return XML{}, http.StatusBadRequest, err } if pf.Allprop == nil && pf.Include != nil { - return propfindXML{}, http.StatusBadRequest, errInvalidPropfind + return XML{}, http.StatusBadRequest, errors.ErrorInvalidPropfind } if pf.Allprop != nil && (pf.Prop != nil || pf.Propname != nil) { - return propfindXML{}, http.StatusBadRequest, errInvalidPropfind + return XML{}, http.StatusBadRequest, errors.ErrorInvalidPropfind } if pf.Prop != nil && pf.Propname != nil { - return propfindXML{}, http.StatusBadRequest, errInvalidPropfind + return XML{}, http.StatusBadRequest, errors.ErrorInvalidPropfind } if pf.Propname == nil && pf.Allprop == nil && pf.Prop == nil { // jfd: I think is perfectly valid ... treat it as allprop - return propfindXML{Allprop: new(struct{})}, 0, nil + return XML{Allprop: new(struct{})}, 0, nil } return pf, 0, nil } -func (s *svc) multistatusResponse(ctx context.Context, pf *propfindXML, mds []*provider.ResourceInfo, ns string, linkshares map[string]struct{}) (string, error) { - responses := make([]*responseXML, 0, len(mds)) +// MultistatusResponse converts a list of resource infos into a multistatus response string +func MultistatusResponse(ctx context.Context, pf *XML, mds []*provider.ResourceInfo, publicURL, ns string, linkshares map[string]struct{}) (string, error) { + responses := make([]*ResponseXML, 0, len(mds)) for i := range mds { - res, err := s.mdToPropResponse(ctx, pf, mds[i], ns, linkshares) + res, err := mdToPropResponse(ctx, pf, mds[i], publicURL, ns, linkshares) if err != nil { return "", err } @@ -544,55 +566,23 @@ func (s *svc) multistatusResponse(ctx context.Context, pf *propfindXML, mds []*p return msg, nil } -func (s *svc) xmlEscaped(val string) []byte { - buf := new(bytes.Buffer) - xml.Escape(buf, []byte(val)) - return buf.Bytes() -} - -func (s *svc) newPropNS(namespace string, local string, val string) *propertyXML { - return &propertyXML{ - XMLName: xml.Name{Space: namespace, Local: local}, - Lang: "", - InnerXML: s.xmlEscaped(val), - } -} - -// TODO properly use the space -func (s *svc) newProp(key, val string) *propertyXML { - return &propertyXML{ - XMLName: xml.Name{Space: "", Local: key}, - Lang: "", - InnerXML: s.xmlEscaped(val), - } -} - -// TODO properly use the space -func (s *svc) newPropRaw(key, val string) *propertyXML { - return &propertyXML{ - XMLName: xml.Name{Space: "", Local: key}, - Lang: "", - InnerXML: []byte(val), - } -} - // mdToPropResponse converts the CS3 metadata into a webdav PropResponse // ns is the CS3 namespace that needs to be removed from the CS3 path before // prefixing it with the baseURI -func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provider.ResourceInfo, ns string, linkshares map[string]struct{}) (*responseXML, error) { +func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, publicURL, ns string, linkshares map[string]struct{}) (*ResponseXML, error) { sublog := appctx.GetLogger(ctx).With().Interface("md", md).Str("ns", ns).Logger() md.Path = strings.TrimPrefix(md.Path, ns) - baseURI := ctx.Value(ctxKeyBaseURI).(string) + baseURI := ctx.Value(net.CtxKeyBaseURI).(string) ref := path.Join(baseURI, md.Path) if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { ref += "/" } - response := responseXML{ - Href: encodePath(ref), - Propstat: []propstatXML{}, + response := ResponseXML{ + Href: net.EncodePath(ref), + Propstat: []PropstatXML{}, } var ls *link.PublicShare @@ -600,7 +590,7 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide // -1 indicates uncalculated // -2 indicates unknown (default) // -3 indicates unlimited - quota := _propQuotaUnknown + quota := net.PropQuotaUnknown size := fmt.Sprintf("%d", md.Size) // TODO refactor helper functions: GetOpaqueJSONEncoded(opaque, key string, *struct) err, GetOpaquePlainEncoded(opaque, key) value, err // or use ok like pattern and return bool? @@ -619,7 +609,7 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide role := conversions.RoleFromResourcePermissions(md.PermissionSet) - isShared := !isCurrentUserOwner(ctx, md.Owner) + isShared := !net.IsCurrentUserOwner(ctx, md.Owner) var wdp string isPublic := ls != nil if md.PermissionSet != nil { @@ -632,13 +622,13 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide sublog.Debug().Interface("role", role).Str("dav-permissions", wdp).Msg("converted PermissionSet") } - propstatOK := propstatXML{ + propstatOK := PropstatXML{ Status: "HTTP/1.1 200 OK", - Prop: []*propertyXML{}, + Prop: []*props.PropertyXML{}, } - propstatNotFound := propstatXML{ + propstatNotFound := PropstatXML{ Status: "HTTP/1.1 404 Not Found", - Prop: []*propertyXML{}, + Prop: []*props.PropertyXML{}, } // when allprops has been requested if pf.Allprop != nil { @@ -647,8 +637,8 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide if md.Id != nil { id := resourceid.OwnCloudResourceIDWrap(md.Id) propstatOK.Prop = append(propstatOK.Prop, - s.newProp("oc:id", id), - s.newProp("oc:fileid", id), + props.NewProp("oc:id", id), + props.NewProp("oc:fileid", id), ) } @@ -656,37 +646,37 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide // etags must be enclosed in double quotes and cannot contain them. // See https://tools.ietf.org/html/rfc7232#section-2.3 for details // TODO(jfd) handle weak tags that start with 'W/' - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getetag", quoteEtag(md.Etag))) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getetag", quoteEtag(md.Etag))) } if md.PermissionSet != nil { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:permissions", wdp)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:permissions", wdp)) } // always return size, well nearly always ... public link shares are a little weird if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("d:resourcetype", "")) + propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("d:resourcetype", "")) if ls == nil { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:size", size)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:size", size)) } // A PROPFIND request SHOULD NOT return DAV:quota-available-bytes and DAV:quota-used-bytes // from https://www.rfc-editor.org/rfc/rfc4331.html#section-2 - // propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:quota-used-bytes", size)) - // propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:quota-available-bytes", quota)) + // propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:quota-used-bytes", size)) + // propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:quota-available-bytes", quota)) } else { propstatOK.Prop = append(propstatOK.Prop, - s.newProp("d:resourcetype", ""), - s.newProp("d:getcontentlength", size), + props.NewProp("d:resourcetype", ""), + props.NewProp("d:getcontentlength", size), ) if md.MimeType != "" { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getcontenttype", md.MimeType)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getcontenttype", md.MimeType)) } } // Finder needs the getLastModified property to work. if md.Mtime != nil { t := utils.TSToTime(md.Mtime).UTC() - lastModifiedString := t.Format(RFC1123) - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getlastmodified", lastModifiedString)) + lastModifiedString := t.Format(net.RFC1123) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getlastmodified", lastModifiedString)) } // stay bug compatible with oc10, see https://github.com/owncloud/core/pull/38304#issuecomment-762185241 @@ -717,20 +707,20 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide } if checksums.Len() > 0 { checksums.WriteString("") - propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("oc:checksums", checksums.String())) + propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("oc:checksums", checksums.String())) } // ls do not report any properties as missing by default if ls == nil { // favorites from arbitrary metadata if k := md.GetArbitraryMetadata(); k == nil { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0")) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", "0")) } else if amd := k.GetMetadata(); amd == nil { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0")) - } else if v, ok := amd[_propOcFavorite]; ok && v != "" { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", v)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", "0")) + } else if v, ok := amd[net.PropOcFavorite]; ok && v != "" { + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", v)) } else { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0")) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", "0")) } } // TODO return other properties ... but how do we put them in a namespace? @@ -738,21 +728,21 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide // otherwise return only the requested properties for i := range pf.Prop { switch pf.Prop[i].Space { - case _nsOwncloud: + case net.NsOwncloud: switch pf.Prop[i].Local { // TODO(jfd): maybe phoenix and the other clients can just use this id as an opaque string? // I tested the desktop client and phoenix to annotate which properties are requestted, see below cases case "fileid": // phoenix only if md.Id != nil { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:fileid", resourceid.OwnCloudResourceIDWrap(md.Id))) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:fileid", resourceid.OwnCloudResourceIDWrap(md.Id))) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:fileid", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:fileid", "")) } case "id": // desktop client only if md.Id != nil { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:id", resourceid.OwnCloudResourceIDWrap(md.Id))) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:id", resourceid.OwnCloudResourceIDWrap(md.Id))) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:id", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:id", "")) } case "permissions": // both // oc:permissions take several char flags to indicate the permissions the user has on this node: @@ -764,78 +754,78 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide // R = Shareable (Reshare) // M = Mounted // in contrast, the ocs:share-permissions further down below indicate clients the maximum permissions that can be granted - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:permissions", wdp)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:permissions", wdp)) case "public-link-permission": // only on a share root node if ls != nil && md.PermissionSet != nil { propstatOK.Prop = append( propstatOK.Prop, - s.newProp("oc:public-link-permission", strconv.FormatUint(uint64(role.OCSPermissions()), 10))) + props.NewProp("oc:public-link-permission", strconv.FormatUint(uint64(role.OCSPermissions()), 10))) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-permission", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:public-link-permission", "")) } case "public-link-item-type": // only on a share root node if ls != nil { if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:public-link-item-type", "folder")) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:public-link-item-type", "folder")) } else { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:public-link-item-type", "file")) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:public-link-item-type", "file")) // redirectref is another option } } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-item-type", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:public-link-item-type", "")) } case "public-link-share-datetime": if ls != nil && ls.Mtime != nil { t := utils.TSToTime(ls.Mtime).UTC() // TODO or ctime? - shareTimeString := t.Format(RFC1123) - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:public-link-share-datetime", shareTimeString)) + shareTimeString := t.Format(net.RFC1123) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:public-link-share-datetime", shareTimeString)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-share-datetime", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:public-link-share-datetime", "")) } case "public-link-share-owner": if ls != nil && ls.Owner != nil { - if isCurrentUserOwner(ctx, ls.Owner) { + if net.IsCurrentUserOwner(ctx, ls.Owner) { u := ctxpkg.ContextMustGetUser(ctx) - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:public-link-share-owner", u.Username)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:public-link-share-owner", u.Username)) } else { u, _ := ctxpkg.ContextGetUser(ctx) sublog.Error().Interface("share", ls).Interface("user", u).Msg("the current user in the context should be the owner of a public link share") - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-share-owner", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:public-link-share-owner", "")) } } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-share-owner", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:public-link-share-owner", "")) } case "public-link-expiration": if ls != nil && ls.Expiration != nil { t := utils.TSToTime(ls.Expiration).UTC() - expireTimeString := t.Format(RFC1123) - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:public-link-expiration", expireTimeString)) + expireTimeString := t.Format(net.RFC1123) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:public-link-expiration", expireTimeString)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-expiration", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:public-link-expiration", "")) } - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:public-link-expiration", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:public-link-expiration", "")) case "size": // phoenix only // TODO we cannot find out if md.Size is set or not because ints in go default to 0 // TODO what is the difference to d:quota-used-bytes (which only exists for collections)? // oc:size is available on files and folders and behaves like d:getcontentlength or d:quota-used-bytes respectively // The hasPrefix is a workaround to make children of the link root show a size if they have 0 bytes if ls == nil || strings.HasPrefix(md.Path, "/"+ls.Token+"/") { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:size", size)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:size", size)) } else { // link share root collection has no size - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:size", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:size", "")) } case "owner-id": // phoenix only if md.Owner != nil { - if isCurrentUserOwner(ctx, md.Owner) { + if net.IsCurrentUserOwner(ctx, md.Owner) { u := ctxpkg.ContextMustGetUser(ctx) - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:owner-id", u.Username)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:owner-id", u.Username)) } else { sublog.Debug().Msg("TODO fetch user username") - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:owner-id", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:owner-id", "")) } } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:owner-id", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:owner-id", "")) } case "favorite": // phoenix only // TODO: can be 0 or 1?, in oc10 it is present or not @@ -843,17 +833,17 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide // TODO: this boolean favorite property is so horribly wrong ... either it is presont, or it is not ... unless ... it is possible to have a non binary value ... we need to double check if ls == nil { if k := md.GetArbitraryMetadata(); k == nil { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0")) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", "0")) } else if amd := k.GetMetadata(); amd == nil { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0")) - } else if v, ok := amd[_propOcFavorite]; ok && v != "" { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "1")) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", "0")) + } else if v, ok := amd[net.PropOcFavorite]; ok && v != "" { + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", "1")) } else { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:favorite", "0")) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", "0")) } } else { // link share root collection has no favorite - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:favorite", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:favorite", "")) } case "checksums": // desktop ... not really ... the desktop sends the OC-Checksum header @@ -885,9 +875,9 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide } if checksums.Len() > 13 { checksums.WriteString("") - propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("oc:checksums", checksums.String())) + propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("oc:checksums", checksums.String())) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:checksums", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:checksums", "")) } case "share-types": // desktop var types strings.Builder @@ -906,21 +896,21 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide } if types.Len() != 0 { - propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("oc:share-types", types.String())) + propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("oc:share-types", types.String())) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:"+pf.Prop[i].Local, "")) } case "owner-display-name": // phoenix only if md.Owner != nil { - if isCurrentUserOwner(ctx, md.Owner) { + if net.IsCurrentUserOwner(ctx, md.Owner) { u := ctxpkg.ContextMustGetUser(ctx) - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:owner-display-name", u.DisplayName)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:owner-display-name", u.DisplayName)) } else { sublog.Debug().Msg("TODO fetch user displayname") - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:owner-display-name", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:owner-display-name", "")) } } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:owner-display-name", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:owner-display-name", "")) } case "downloadURL": // desktop if isPublic && md.Type == provider.ResourceType_RESOURCE_TYPE_FILE { @@ -939,9 +929,9 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide path = sb.String() } - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:downloadURL", s.c.PublicURL+baseURI+path)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:downloadURL", publicURL+baseURI+path)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:"+pf.Prop[i].Local, "")) } case "signature-auth": if isPublic { @@ -956,9 +946,9 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide sb.WriteString(expiration.Format(time.RFC3339)) sb.WriteString("") - propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("oc:signature-auth", sb.String())) + propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("oc:signature-auth", sb.String())) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:signature-auth", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:signature-auth", "")) } } case "privatelink": // phoenix only @@ -974,15 +964,15 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide // TODO(jfd): double check the client behavior with reva on backup restore fallthrough default: - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:"+pf.Prop[i].Local, "")) } - case _nsDav: + case net.NsDav: switch pf.Prop[i].Local { case "getetag": // both if md.Etag != "" { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getetag", quoteEtag(md.Etag))) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getetag", quoteEtag(md.Etag))) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:getetag", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:getetag", "")) } case "getcontentlength": // both // see everts stance on this https://stackoverflow.com/a/31621912, he points to http://tools.ietf.org/html/rfc4918#section-15.3 @@ -991,53 +981,53 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide // which is not the case ... so we don't return it on collections. owncloud has oc:size for that // TODO we cannot find out if md.Size is set or not because ints in go default to 0 if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:getcontentlength", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:getcontentlength", "")) } else { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getcontentlength", size)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getcontentlength", size)) } case "resourcetype": // both if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("d:resourcetype", "")) + propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("d:resourcetype", "")) } else { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:resourcetype", "")) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:resourcetype", "")) // redirectref is another option } case "getcontenttype": // phoenix if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { // directories have no contenttype - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:getcontenttype", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:getcontenttype", "")) } else if md.MimeType != "" { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getcontenttype", md.MimeType)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getcontenttype", md.MimeType)) } case "getlastmodified": // both // TODO we cannot find out if md.Mtime is set or not because ints in go default to 0 if md.Mtime != nil { t := utils.TSToTime(md.Mtime).UTC() - lastModifiedString := t.Format(RFC1123) - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getlastmodified", lastModifiedString)) + lastModifiedString := t.Format(net.RFC1123) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getlastmodified", lastModifiedString)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:getlastmodified", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:getlastmodified", "")) } case "quota-used-bytes": // RFC 4331 if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { // always returns the current usage, // in oc10 there seems to be a bug that makes the size in webdav differ from the one in the user properties, not taking shares into account // in ocis we plan to always mak the quota a property of the storage space - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:quota-used-bytes", size)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:quota-used-bytes", size)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:quota-used-bytes", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:quota-used-bytes", "")) } case "quota-available-bytes": // RFC 4331 if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { // oc10 returns -3 for unlimited, -2 for unknown, -1 for uncalculated - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:quota-available-bytes", quota)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:quota-available-bytes", quota)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:quota-available-bytes", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:quota-available-bytes", "")) } default: - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:"+pf.Prop[i].Local, "")) } - case _nsOCS: + case net.NsOCS: switch pf.Prop[i].Local { // ocs:share-permissions indicate clients the maximum permissions that can be granted: // 1 = read @@ -1054,21 +1044,21 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide perms &^= conversions.PermissionCreate perms &^= conversions.PermissionDelete } - propstatOK.Prop = append(propstatOK.Prop, s.newPropNS(pf.Prop[i].Space, pf.Prop[i].Local, strconv.FormatUint(uint64(perms), 10))) + propstatOK.Prop = append(propstatOK.Prop, props.NewPropNS(pf.Prop[i].Space, pf.Prop[i].Local, strconv.FormatUint(uint64(perms), 10))) } default: - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:"+pf.Prop[i].Local, "")) } default: // handle custom properties if k := md.GetArbitraryMetadata(); k == nil { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newPropNS(pf.Prop[i].Space, pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewPropNS(pf.Prop[i].Space, pf.Prop[i].Local, "")) } else if amd := k.GetMetadata(); amd == nil { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newPropNS(pf.Prop[i].Space, pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewPropNS(pf.Prop[i].Space, pf.Prop[i].Local, "")) } else if v, ok := amd[metadataKeyOf(&pf.Prop[i])]; ok && v != "" { - propstatOK.Prop = append(propstatOK.Prop, s.newPropNS(pf.Prop[i].Space, pf.Prop[i].Local, v)) + propstatOK.Prop = append(propstatOK.Prop, props.NewPropNS(pf.Prop[i].Space, pf.Prop[i].Local, v)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newPropNS(pf.Prop[i].Space, pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewPropNS(pf.Prop[i].Space, pf.Prop[i].Local, "")) } } } @@ -1092,17 +1082,6 @@ func quoteEtag(etag string) string { return `"` + strings.Trim(etag, `"`) + `"` } -// a file is only yours if you are the owner -func isCurrentUserOwner(ctx context.Context, owner *userv1beta1.UserId) bool { - contextUser, ok := ctxpkg.ContextGetUser(ctx) - if ok && contextUser.Id != nil && owner != nil && - contextUser.Id.Idp == owner.Idp && - contextUser.Id.OpaqueId == owner.OpaqueId { - return true - } - return false -} - type countingReader struct { n int r io.Reader @@ -1116,23 +1095,24 @@ func (c *countingReader) Read(p []byte) (int, error) { func metadataKeyOf(n *xml.Name) string { switch { - case n.Space == _nsDav && n.Local == "quota-available-bytes": + case n.Space == net.NsDav && n.Local == "quota-available-bytes": return "quota" default: return fmt.Sprintf("%s/%s", n.Space, n.Local) } } +// Props represents properties related to a resource // http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for propfind) -type propfindProps []xml.Name +type Props []xml.Name // UnmarshalXML appends the property names enclosed within start to pn. // // It returns an error if start does not contain any properties or if // properties contain values. Character data between properties is ignored. -func (pn *propfindProps) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { +func (pn *Props) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { for { - t, err := next(d) + t, err := props.Next(d) if err != nil { return err } @@ -1146,7 +1126,7 @@ func (pn *propfindProps) UnmarshalXML(d *xml.Decoder, start xml.StartElement) er */ return nil case xml.StartElement: - t, err = next(d) + t, err = props.Next(d) if err != nil { return err } @@ -1158,53 +1138,36 @@ func (pn *propfindProps) UnmarshalXML(d *xml.Decoder, start xml.StartElement) er } } +// XML holds the xml representation of a propfind // http://www.webdav.org/specs/rfc4918.html#ELEMENT_propfind -type propfindXML struct { - XMLName xml.Name `xml:"DAV: propfind"` - Allprop *struct{} `xml:"DAV: allprop"` - Propname *struct{} `xml:"DAV: propname"` - Prop propfindProps `xml:"DAV: prop"` - Include propfindProps `xml:"DAV: include"` +type XML struct { + XMLName xml.Name `xml:"DAV: propfind"` + Allprop *struct{} `xml:"DAV: allprop"` + Propname *struct{} `xml:"DAV: propname"` + Prop Props `xml:"DAV: prop"` + Include Props `xml:"DAV: include"` } -type responseXML struct { - XMLName xml.Name `xml:"d:response"` - Href string `xml:"d:href"` - Propstat []propstatXML `xml:"d:propstat"` - Status string `xml:"d:status,omitempty"` - Error *errorXML `xml:"d:error"` - ResponseDescription string `xml:"d:responsedescription,omitempty"` +// ResponseXML holds the xml representation of a propfind response +type ResponseXML struct { + XMLName xml.Name `xml:"d:response"` + Href string `xml:"d:href"` + Propstat []PropstatXML `xml:"d:propstat"` + Status string `xml:"d:status,omitempty"` + Error *errors.ErrorXML `xml:"d:error"` + ResponseDescription string `xml:"d:responsedescription,omitempty"` } +// PropstatXML holds the xml representation of a propfind response // http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat -type propstatXML struct { +type PropstatXML struct { // Prop requires DAV: to be the default namespace in the enclosing // XML. This is due to the standard encoding/xml package currently // not honoring namespace declarations inside a xmltag with a // parent element for anonymous slice elements. // Use of multistatusWriter takes care of this. - Prop []*propertyXML `xml:"d:prop>_ignored_"` - Status string `xml:"d:status"` - Error *errorXML `xml:"d:error"` - ResponseDescription string `xml:"d:responsedescription,omitempty"` -} - -// Property represents a single DAV resource property as defined in RFC 4918. -// http://www.webdav.org/specs/rfc4918.html#data.model.for.resource.properties -type propertyXML struct { - // XMLName is the fully qualified name that identifies this property. - XMLName xml.Name - - // Lang is an optional xml:lang attribute. - Lang string `xml:"xml:lang,attr,omitempty"` - - // InnerXML contains the XML representation of the property value. - // See http://www.webdav.org/specs/rfc4918.html#property_values - // - // Property values of complex type or mixed-content must have fully - // expanded XML namespaces or be self-contained with according - // XML namespace declarations. They must not rely on any XML - // namespace declarations within the scope of the XML document, - // even including the DAV: namespace. - InnerXML []byte `xml:",innerxml"` + Prop []*props.PropertyXML `xml:"d:prop>_ignored_"` + Status string `xml:"d:status"` + Error *errors.ErrorXML `xml:"d:error"` + ResponseDescription string `xml:"d:responsedescription,omitempty"` } diff --git a/internal/http/services/owncloud/ocdav/propfind/propfind_suite_test.go b/internal/http/services/owncloud/ocdav/propfind/propfind_suite_test.go new file mode 100644 index 00000000000..d02dba936bd --- /dev/null +++ b/internal/http/services/owncloud/ocdav/propfind/propfind_suite_test.go @@ -0,0 +1,31 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package propfind_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestPropfind(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Propfind Suite") +} diff --git a/internal/http/services/owncloud/ocdav/propfind/propfind_test.go b/internal/http/services/owncloud/ocdav/propfind/propfind_test.go new file mode 100644 index 00000000000..0109fb82a89 --- /dev/null +++ b/internal/http/services/owncloud/ocdav/propfind/propfind_test.go @@ -0,0 +1,73 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package propfind_test + +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + + sprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/propfind" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/propfind/mocks" + "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/stretchr/testify/mock" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Propfind", func() { + var ( + handler *propfind.Handler + client *mocks.GatewayClient + ctx context.Context + ) + + JustBeforeEach(func() { + ctx = context.Background() + client = &mocks.GatewayClient{} + handler = propfind.NewHandler("127.0.0.1:3000", func() (propfind.GatewayClient, error) { + return client, nil + }) + }) + + Describe("NewHandler", func() { + It("returns a handler", func() { + Expect(handler).ToNot(BeNil()) + }) + }) + + Describe("HandleSpacesPropfind", func() { + It("handles invalid space ids", func() { + client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&sprovider.ListStorageSpacesResponse{ + Status: status.NewOK(ctx), + StorageSpaces: []*sprovider.StorageSpace{}, + }, nil) + + rr := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/", strings.NewReader("")) + Expect(err).ToNot(HaveOccurred()) + + handler.HandleSpacesPropfind(rr, req, "foo") + Expect(rr.Code).To(Equal(http.StatusNotFound)) + }) + }) +}) diff --git a/internal/http/services/owncloud/ocdav/proppatch.go b/internal/http/services/owncloud/ocdav/proppatch.go index 73495868913..0846579e28c 100644 --- a/internal/http/services/owncloud/ocdav/proppatch.go +++ b/internal/http/services/owncloud/ocdav/proppatch.go @@ -29,10 +29,14 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/propfind" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/props" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/pkg/appctx" ctxpkg "github.com/cs3org/reva/pkg/ctx" rtrace "github.com/cs3org/reva/pkg/trace" - "github.com/pkg/errors" "github.com/rs/zerolog" ) @@ -49,11 +53,8 @@ func (s *svc) handlePathProppatch(w http.ResponseWriter, r *http.Request, ns str sublog.Debug().Err(err).Msg("error reading proppatch") w.WriteHeader(status) m := fmt.Sprintf("Error reading proppatch: %v", err) - b, err := Marshal(exception{ - code: SabredavBadRequest, - message: m, - }) - HandleWebdavError(&sublog, w, b, err) + b, err := errors.Marshal(errors.SabredavBadRequest, m, "") + errors.HandleWebdavError(&sublog, w, b, err) return } @@ -64,18 +65,18 @@ func (s *svc) handlePathProppatch(w http.ResponseWriter, r *http.Request, ns str return } - space, rpcStatus, err := s.lookUpStorageSpaceForPath(ctx, fn) + space, rpcStatus, err := spacelookup.LookUpStorageSpaceForPath(ctx, c, fn) if err != nil { sublog.Error().Err(err).Str("path", fn).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if rpcStatus.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, rpcStatus) + errors.HandleErrorStatus(&sublog, w, rpcStatus) return } // check if resource exists - statReq := &provider.StatRequest{Ref: makeRelativeReference(space, fn, false)} + statReq := &provider.StatRequest{Ref: spacelookup.MakeRelativeReference(space, fn, false)} statRes, err := c.Stat(ctx, statReq) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc stat request") @@ -87,24 +88,21 @@ func (s *svc) handlePathProppatch(w http.ResponseWriter, r *http.Request, ns str if statRes.Status.Code == rpc.Code_CODE_NOT_FOUND { w.WriteHeader(http.StatusNotFound) m := fmt.Sprintf("Resource %v not found", fn) - b, err := Marshal(exception{ - code: SabredavNotFound, - message: m, - }) - HandleWebdavError(&sublog, w, b, err) + b, err := errors.Marshal(errors.SabredavNotFound, m, "") + errors.HandleWebdavError(&sublog, w, b, err) } - HandleErrorStatus(&sublog, w, statRes.Status) + errors.HandleErrorStatus(&sublog, w, statRes.Status) return } - acceptedProps, removedProps, ok := s.handleProppatch(ctx, w, r, makeRelativeReference(space, fn, false), pp, sublog) + acceptedProps, removedProps, ok := s.handleProppatch(ctx, w, r, spacelookup.MakeRelativeReference(space, fn, false), pp, sublog) if !ok { // handleProppatch handles responses in error cases so we can just return return } nRef := strings.TrimPrefix(fn, ns) - nRef = path.Join(ctx.Value(ctxKeyBaseURI).(string), nRef) + nRef = path.Join(ctx.Value(net.CtxKeyBaseURI).(string), nRef) if statRes.Info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { nRef += "/" } @@ -118,6 +116,13 @@ func (s *svc) handleSpacesProppatch(w http.ResponseWriter, r *http.Request, spac sublog := appctx.GetLogger(ctx).With().Str("path", r.URL.Path).Str("spaceid", spaceID).Logger() + c, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } + pp, status, err := readProppatch(r.Body) if err != nil { sublog.Debug().Err(err).Msg("error reading proppatch") @@ -126,7 +131,7 @@ func (s *svc) handleSpacesProppatch(w http.ResponseWriter, r *http.Request, spac } // retrieve a specific storage space - ref, rpcStatus, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path, true) + ref, rpcStatus, err := spacelookup.LookUpStorageSpaceReference(ctx, c, spaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -134,16 +139,10 @@ func (s *svc) handleSpacesProppatch(w http.ResponseWriter, r *http.Request, spac } if rpcStatus.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, rpcStatus) + errors.HandleErrorStatus(&sublog, w, rpcStatus) return } - c, err := s.getClient() - if err != nil { - sublog.Error().Err(err).Msg("error getting grpc client") - w.WriteHeader(http.StatusInternalServerError) - return - } // check if resource exists statReq := &provider.StatRequest{ Ref: ref, @@ -156,7 +155,7 @@ func (s *svc) handleSpacesProppatch(w http.ResponseWriter, r *http.Request, spac } if statRes.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, statRes.Status) + errors.HandleErrorStatus(&sublog, w, statRes.Status) return } @@ -167,7 +166,7 @@ func (s *svc) handleSpacesProppatch(w http.ResponseWriter, r *http.Request, spac } nRef := path.Join(spaceID, statRes.Info.Path) - nRef = path.Join(ctx.Value(ctxKeyBaseURI).(string), nRef) + nRef = path.Join(ctx.Value(net.CtxKeyBaseURI).(string), nRef) if statRes.Info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { nRef += "/" } @@ -232,14 +231,11 @@ func (s *svc) handleProppatch(ctx context.Context, w http.ResponseWriter, r *htt if res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { w.WriteHeader(http.StatusForbidden) m := fmt.Sprintf("Permission denied to remove properties on resource %v", ref.Path) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: m, - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + errors.HandleWebdavError(&log, w, b, err) return nil, nil, false } - HandleErrorStatus(&log, w, res.Status) + errors.HandleErrorStatus(&log, w, res.Status) return nil, nil, false } if key == "http://owncloud.org/ns/favorite" { @@ -269,14 +265,11 @@ func (s *svc) handleProppatch(ctx context.Context, w http.ResponseWriter, r *htt if res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { w.WriteHeader(http.StatusForbidden) m := fmt.Sprintf("Permission denied to set properties on resource %v", ref.Path) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: m, - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + errors.HandleWebdavError(&log, w, b, err) return nil, nil, false } - HandleErrorStatus(&log, w, res.Status) + errors.HandleErrorStatus(&log, w, res.Status) return nil, nil, false } @@ -313,8 +306,8 @@ func (s *svc) handleProppatchResponse(ctx context.Context, w http.ResponseWriter w.WriteHeader(http.StatusInternalServerError) return } - w.Header().Set(HeaderDav, "1, 3, extended-mkcol") - w.Header().Set(HeaderContentType, "application/xml; charset=utf-8") + w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol") + w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8") w.WriteHeader(http.StatusMultiStatus) if _, err := w.Write([]byte(propRes)); err != nil { log.Err(err).Msg("error writing response") @@ -322,29 +315,29 @@ func (s *svc) handleProppatchResponse(ctx context.Context, w http.ResponseWriter } func (s *svc) formatProppatchResponse(ctx context.Context, acceptedProps []xml.Name, removedProps []xml.Name, ref string) (string, error) { - responses := make([]responseXML, 0, 1) - response := responseXML{ - Href: encodePath(ref), - Propstat: []propstatXML{}, + responses := make([]propfind.ResponseXML, 0, 1) + response := propfind.ResponseXML{ + Href: net.EncodePath(ref), + Propstat: []propfind.PropstatXML{}, } if len(acceptedProps) > 0 { - propstatBody := []*propertyXML{} + propstatBody := []*props.PropertyXML{} for i := range acceptedProps { - propstatBody = append(propstatBody, s.newPropNS(acceptedProps[i].Space, acceptedProps[i].Local, "")) + propstatBody = append(propstatBody, props.NewPropNS(acceptedProps[i].Space, acceptedProps[i].Local, "")) } - response.Propstat = append(response.Propstat, propstatXML{ + response.Propstat = append(response.Propstat, propfind.PropstatXML{ Status: "HTTP/1.1 200 OK", Prop: propstatBody, }) } if len(removedProps) > 0 { - propstatBody := []*propertyXML{} + propstatBody := []*props.PropertyXML{} for i := range removedProps { - propstatBody = append(propstatBody, s.newPropNS(removedProps[i].Space, removedProps[i].Local, "")) + propstatBody = append(propstatBody, props.NewPropNS(removedProps[i].Space, removedProps[i].Local, "")) } - response.Propstat = append(response.Propstat, propstatXML{ + response.Propstat = append(response.Propstat, propfind.PropstatXML{ Status: "HTTP/1.1 204 No Content", Prop: propstatBody, }) @@ -364,7 +357,7 @@ func (s *svc) formatProppatchResponse(ctx context.Context, acceptedProps []xml.N func (s *svc) isBooleanProperty(prop string) bool { // TODO add other properties we know to be boolean? - return prop == _propOcFavorite + return prop == net.PropOcFavorite } func (s *svc) as0or1(val string) string { @@ -390,11 +383,11 @@ type Proppatch struct { // remove them, it sets them. Remove bool // Props contains the properties to be set or removed. - Props []propertyXML + Props []props.PropertyXML } // http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for proppatch) -type proppatchProps []propertyXML +type proppatchProps []props.PropertyXML // UnmarshalXML appends the property names and values enclosed within start // to ps. @@ -407,7 +400,7 @@ type proppatchProps []propertyXML func (ps *proppatchProps) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { lang := xmlLang(start, "") for { - t, err := next(d) + t, err := props.Next(d) if err != nil { return err } @@ -418,7 +411,7 @@ func (ps *proppatchProps) UnmarshalXML(d *xml.Decoder, start xml.StartElement) e } return nil case xml.StartElement: - p := propertyXML{} + p := props.PropertyXML{} err = d.DecodeElement(&p, &elem) if err != nil { return err @@ -453,17 +446,17 @@ func readProppatch(r io.Reader) (patches []Proppatch, status int, err error) { for _, op := range pu.SetRemove { remove := false switch op.XMLName { - case xml.Name{Space: _nsDav, Local: "set"}: + case xml.Name{Space: net.NsDav, Local: "set"}: // No-op. - case xml.Name{Space: _nsDav, Local: "remove"}: + case xml.Name{Space: net.NsDav, Local: "remove"}: for _, p := range op.Prop { if len(p.InnerXML) > 0 { - return nil, http.StatusBadRequest, errInvalidProppatch + return nil, http.StatusBadRequest, errors.ErrInvalidProppatch } } remove = true default: - return nil, http.StatusBadRequest, errInvalidProppatch + return nil, http.StatusBadRequest, errors.ErrInvalidProppatch } patches = append(patches, Proppatch{Remove: remove, Props: op.Prop}) } @@ -480,25 +473,3 @@ func xmlLang(s xml.StartElement, d string) string { } return d } - -// Next returns the next token, if any, in the XML stream of d. -// RFC 4918 requires to ignore comments, processing instructions -// and directives. -// http://www.webdav.org/specs/rfc4918.html#property_values -// http://www.webdav.org/specs/rfc4918.html#xml-extensibility -func next(d *xml.Decoder) (xml.Token, error) { - for { - t, err := d.Token() - if err != nil { - return t, err - } - switch t.(type) { - case xml.Comment, xml.Directive, xml.ProcInst: - continue - default: - return t, nil - } - } -} - -var errInvalidProppatch = errors.New("webdav: invalid proppatch") diff --git a/internal/http/services/owncloud/ocdav/props/props.go b/internal/http/services/owncloud/ocdav/props/props.go new file mode 100644 index 00000000000..54142cbd979 --- /dev/null +++ b/internal/http/services/owncloud/ocdav/props/props.go @@ -0,0 +1,99 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package props + +import ( + "bytes" + "encoding/xml" +) + +// PropertyXML represents a single DAV resource property as defined in RFC 4918. +// http://www.webdav.org/specs/rfc4918.html#data.model.for.resource.properties +type PropertyXML struct { + // XMLName is the fully qualified name that identifies this property. + XMLName xml.Name + + // Lang is an optional xml:lang attribute. + Lang string `xml:"xml:lang,attr,omitempty"` + + // InnerXML contains the XML representation of the property value. + // See http://www.webdav.org/specs/rfc4918.html#property_values + // + // Property values of complex type or mixed-content must have fully + // expanded XML namespaces or be self-contained with according + // XML namespace declarations. They must not rely on any XML + // namespace declarations within the scope of the XML document, + // even including the DAV: namespace. + InnerXML []byte `xml:",innerxml"` +} + +func xmlEscaped(val string) []byte { + buf := new(bytes.Buffer) + xml.Escape(buf, []byte(val)) + return buf.Bytes() +} + +// NewPropNS returns a new PropertyXML instance +func NewPropNS(namespace string, local string, val string) *PropertyXML { + return &PropertyXML{ + XMLName: xml.Name{Space: namespace, Local: local}, + Lang: "", + InnerXML: xmlEscaped(val), + } +} + +// NewProp returns a new PropertyXML instance while xml-escaping the value +// TODO properly use the space +func NewProp(key, val string) *PropertyXML { + return &PropertyXML{ + XMLName: xml.Name{Space: "", Local: key}, + Lang: "", + InnerXML: xmlEscaped(val), + } +} + +// NewPropRaw returns a new PropertyXML instance for the given key/value pair +// TODO properly use the space +func NewPropRaw(key, val string) *PropertyXML { + return &PropertyXML{ + XMLName: xml.Name{Space: "", Local: key}, + Lang: "", + InnerXML: []byte(val), + } +} + +// Next returns the next token, if any, in the XML stream of d. +// RFC 4918 requires to ignore comments, processing instructions +// and directives. +// http://www.webdav.org/specs/rfc4918.html#property_values +// http://www.webdav.org/specs/rfc4918.html#xml-extensibility +func Next(d *xml.Decoder) (xml.Token, error) { + for { + t, err := d.Token() + if err != nil { + return t, err + } + switch t.(type) { + case xml.Comment, xml.Directive, xml.ProcInst: + continue + default: + return t, nil + } + } +} diff --git a/internal/http/services/owncloud/ocdav/publicfile.go b/internal/http/services/owncloud/ocdav/publicfile.go index 183291ccd54..e64ceb92995 100644 --- a/internal/http/services/owncloud/ocdav/publicfile.go +++ b/internal/http/services/owncloud/ocdav/publicfile.go @@ -25,6 +25,8 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/propfind" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rhttp/router" rtrace "github.com/cs3org/reva/pkg/trace" @@ -90,7 +92,7 @@ func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns s sublog := appctx.GetLogger(ctx).With().Interface("tokenStatInfo", tokenStatInfo).Logger() sublog.Debug().Msg("handlePropfindOnToken") - depth := r.Header.Get(HeaderDepth) + depth := r.Header.Get(net.HeaderDepth) if depth == "" { depth = "1" } @@ -102,7 +104,7 @@ func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns s return } - pf, status, err := readPropfind(r.Body) + pf, status, err := propfind.ReadPropfind(r.Body) if err != nil { sublog.Debug().Err(err).Msg("error reading propfind request") w.WriteHeader(status) @@ -114,15 +116,15 @@ func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns s infos := s.getPublicFileInfos(onContainer, depth == "0", tokenStatInfo) - propRes, err := s.multistatusResponse(ctx, &pf, infos, ns, nil) + propRes, err := propfind.MultistatusResponse(ctx, &pf, infos, s.c.PublicURL, ns, nil) if err != nil { sublog.Error().Err(err).Msg("error formatting propfind") w.WriteHeader(http.StatusInternalServerError) return } - w.Header().Set(HeaderDav, "1, 3, extended-mkcol") - w.Header().Set(HeaderContentType, "application/xml; charset=utf-8") + w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol") + w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8") w.WriteHeader(http.StatusMultiStatus) if _, err := w.Write([]byte(propRes)); err != nil { sublog.Err(err).Msg("error writing response") diff --git a/internal/http/services/owncloud/ocdav/put.go b/internal/http/services/owncloud/ocdav/put.go index f7b0dae4e1a..9dc53e38a89 100644 --- a/internal/http/services/owncloud/ocdav/put.go +++ b/internal/http/services/owncloud/ocdav/put.go @@ -30,6 +30,9 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/internal/http/services/datagateway" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/rhttp" @@ -41,7 +44,7 @@ import ( ) func sufferMacOSFinder(r *http.Request) bool { - return r.Header.Get(HeaderExpectedEntityLength) != "" + return r.Header.Get(net.HeaderExpectedEntityLength) != "" } func handleMacOSFinder(w http.ResponseWriter, r *http.Request) error { @@ -63,8 +66,8 @@ func handleMacOSFinder(w http.ResponseWriter, r *http.Request) error { */ log := appctx.GetLogger(r.Context()) - content := r.Header.Get(HeaderContentLength) - expected := r.Header.Get(HeaderExpectedEntityLength) + content := r.Header.Get(net.HeaderContentLength) + expected := r.Header.Get(net.HeaderExpectedEntityLength) log.Warn().Str("content-length", content).Str("x-expected-entity-length", expected).Msg("Mac OS Finder corner-case detected") // The best mitigation to this problem is to tell users to not use crappy Finder. @@ -102,7 +105,7 @@ func isContentRange(r *http.Request) bool { in unexpected behaviour (cf PEAR::HTTP_WebDAV_Client 1.0.1), we reject all PUT requests with a Content-Range for now. */ - return r.Header.Get(HeaderContentRange) != "" + return r.Header.Get(net.HeaderContentRange) != "" } func (s *svc) handlePathPut(w http.ResponseWriter, r *http.Request, ns string) { @@ -112,18 +115,24 @@ func (s *svc) handlePathPut(w http.ResponseWriter, r *http.Request, ns string) { fn := path.Join(ns, r.URL.Path) sublog := appctx.GetLogger(ctx).With().Str("path", fn).Logger() - space, status, err := s.lookUpStorageSpaceForPath(ctx, fn) + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } + space, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, fn) if err != nil { sublog.Error().Err(err).Str("path", fn).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } - s.handlePut(ctx, w, r, makeRelativeReference(space, fn, false), sublog) + s.handlePut(ctx, w, r, spacelookup.MakeRelativeReference(space, fn, false), sublog) } func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Request, ref *provider.Reference, log zerolog.Logger) { @@ -153,7 +162,7 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ return } if sRes.Status.Code != rpc.Code_CODE_OK && sRes.Status.Code != rpc.Code_CODE_NOT_FOUND { - HandleErrorStatus(&log, w, sRes.Status) + errors.HandleErrorStatus(&log, w, sRes.Status) return } @@ -164,7 +173,7 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ w.WriteHeader(http.StatusConflict) return } - clientETag := r.Header.Get(HeaderIfMatch) + clientETag := r.Header.Get(net.HeaderIfMatch) serverETag := info.Etag if clientETag != "" { if clientETag != serverETag { @@ -176,27 +185,27 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ } opaqueMap := map[string]*typespb.OpaqueEntry{ - HeaderUploadLength: { + net.HeaderUploadLength: { Decoder: "plain", Value: []byte(strconv.FormatInt(length, 10)), }, } - if mtime := r.Header.Get(HeaderOCMtime); mtime != "" { - opaqueMap[HeaderOCMtime] = &typespb.OpaqueEntry{ + if mtime := r.Header.Get(net.HeaderOCMtime); mtime != "" { + opaqueMap[net.HeaderOCMtime] = &typespb.OpaqueEntry{ Decoder: "plain", Value: []byte(mtime), } // TODO: find a way to check if the storage really accepted the value - w.Header().Set(HeaderOCMtime, "accepted") + w.Header().Set(net.HeaderOCMtime, "accepted") } // curl -X PUT https://demo.owncloud.com/remote.php/webdav/testcs.bin -u demo:demo -d '123' -v -H 'OC-Checksum: SHA1:40bd001563085fc35165329ea1ff5c5ecbdbbeef' var cparts []string // TUS Upload-Checksum header takes precedence - if checksum := r.Header.Get(HeaderUploadChecksum); checksum != "" { + if checksum := r.Header.Get(net.HeaderUploadChecksum); checksum != "" { cparts = strings.SplitN(checksum, " ", 2) if len(cparts) != 2 { log.Debug().Str("upload-checksum", checksum).Msg("invalid Upload-Checksum format, expected '[algorithm] [checksum]'") @@ -204,7 +213,7 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ return } // Then try owncloud header - } else if checksum := r.Header.Get(HeaderOCChecksum); checksum != "" { + } else if checksum := r.Header.Get(net.HeaderOCChecksum); checksum != "" { cparts = strings.SplitN(checksum, ":", 2) if len(cparts) != 2 { log.Debug().Str("oc-checksum", checksum).Msg("invalid OC-Checksum format, expected '[algorithm]:[checksum]'") @@ -215,7 +224,7 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ // we do not check the algorithm here, because it might depend on the storage if len(cparts) == 2 { // Translate into TUS style Upload-Checksum header - opaqueMap[HeaderUploadChecksum] = &typespb.OpaqueEntry{ + opaqueMap[net.HeaderUploadChecksum] = &typespb.OpaqueEntry{ Decoder: "plain", // algorithm is always lowercase, checksum is separated by space Value: []byte(strings.ToLower(cparts[0]) + " " + cparts[1]), @@ -239,15 +248,12 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ switch uRes.Status.Code { case rpc.Code_CODE_PERMISSION_DENIED: w.WriteHeader(http.StatusForbidden) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: "permission denied: you have no permission to upload content", - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, "permission denied: you have no permission to upload content", "") + errors.HandleWebdavError(&log, w, b, err) case rpc.Code_CODE_NOT_FOUND: w.WriteHeader(http.StatusConflict) default: - HandleErrorStatus(&log, w, uRes.Status) + errors.HandleErrorStatus(&log, w, uRes.Status) } return } @@ -283,11 +289,8 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ } if httpRes.StatusCode == errtypes.StatusChecksumMismatch { w.WriteHeader(http.StatusBadRequest) - b, err := Marshal(exception{ - code: SabredavBadRequest, - message: "The computed checksum does not match the one received from the client.", - }) - HandleWebdavError(&log, w, b, err) + b, err := errors.Marshal(errors.SabredavBadRequest, "The computed checksum does not match the one received from the client.", "") + errors.HandleWebdavError(&log, w, b, err) return } log.Error().Err(err).Msg("PUT request to data server failed") @@ -321,19 +324,19 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ } if sRes.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&log, w, sRes.Status) + errors.HandleErrorStatus(&log, w, sRes.Status) return } newInfo := sRes.Info - w.Header().Add(HeaderContentType, newInfo.MimeType) - w.Header().Set(HeaderETag, newInfo.Etag) - w.Header().Set(HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(newInfo.Id)) - w.Header().Set(HeaderOCETag, newInfo.Etag) + w.Header().Add(net.HeaderContentType, newInfo.MimeType) + w.Header().Set(net.HeaderETag, newInfo.Etag) + w.Header().Set(net.HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(newInfo.Id)) + w.Header().Set(net.HeaderOCETag, newInfo.Etag) t := utils.TSToTime(newInfo.Mtime).UTC() lastModifiedString := t.Format(time.RFC1123Z) - w.Header().Set(HeaderLastModified, lastModifiedString) + w.Header().Set(net.HeaderLastModified, lastModifiedString) // file was new if info == nil { @@ -350,8 +353,14 @@ func (s *svc) handleSpacesPut(w http.ResponseWriter, r *http.Request, spaceID st defer span.End() sublog := appctx.GetLogger(ctx).With().Str("spaceid", spaceID).Str("path", r.URL.Path).Logger() + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } - spaceRef, status, err := s.lookUpStorageSpaceReference(ctx, spaceID, r.URL.Path, true) + spaceRef, status, err := spacelookup.LookUpStorageSpaceReference(ctx, client, spaceID, r.URL.Path, true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -359,7 +368,7 @@ func (s *svc) handleSpacesPut(w http.ResponseWriter, r *http.Request, spaceID st } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } @@ -385,10 +394,10 @@ func checkPreconditions(w http.ResponseWriter, r *http.Request, log zerolog.Logg } func getContentLength(w http.ResponseWriter, r *http.Request) (int64, error) { - length, err := strconv.ParseInt(r.Header.Get(HeaderContentLength), 10, 64) + length, err := strconv.ParseInt(r.Header.Get(net.HeaderContentLength), 10, 64) if err != nil { // Fallback to Upload-Length - length, err = strconv.ParseInt(r.Header.Get(HeaderUploadLength), 10, 64) + length, err = strconv.ParseInt(r.Header.Get(net.HeaderUploadLength), 10, 64) if err != nil { return 0, err } diff --git a/internal/http/services/owncloud/ocdav/report.go b/internal/http/services/owncloud/ocdav/report.go index d25f7280d06..85feefb7d50 100644 --- a/internal/http/services/owncloud/ocdav/report.go +++ b/internal/http/services/owncloud/ocdav/report.go @@ -26,6 +26,8 @@ import ( rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/propfind" "github.com/cs3org/reva/pkg/appctx" ctxpkg "github.com/cs3org/reva/pkg/ctx" ) @@ -118,14 +120,14 @@ func (s *svc) doFilterFiles(w http.ResponseWriter, r *http.Request, ff *reportFi infos = append(infos, statRes.Info) } - responsesXML, err := s.multistatusResponse(ctx, &propfindXML{Prop: ff.Prop}, infos, namespace, nil) + responsesXML, err := propfind.MultistatusResponse(ctx, &propfind.XML{Prop: ff.Prop}, infos, s.c.PublicURL, namespace, nil) if err != nil { log.Error().Err(err).Msg("error formatting propfind") w.WriteHeader(http.StatusInternalServerError) return } - w.Header().Set(HeaderDav, "1, 3, extended-mkcol") - w.Header().Set(HeaderContentType, "application/xml; charset=utf-8") + w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol") + w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8") w.WriteHeader(http.StatusMultiStatus) if _, err := w.Write([]byte(responsesXML)); err != nil { log.Err(err).Msg("error writing response") @@ -141,7 +143,7 @@ type report struct { type reportSearchFiles struct { XMLName xml.Name `xml:"search-files"` Lang string `xml:"xml:lang,attr,omitempty"` - Prop propfindProps `xml:"DAV: prop"` + Prop propfind.Props `xml:"DAV: prop"` Search reportSearchFilesSearch `xml:"search"` } type reportSearchFilesSearch struct { @@ -153,7 +155,7 @@ type reportSearchFilesSearch struct { type reportFilterFiles struct { XMLName xml.Name `xml:"filter-files"` Lang string `xml:"xml:lang,attr,omitempty"` - Prop propfindProps `xml:"DAV: prop"` + Prop propfind.Props `xml:"DAV: prop"` Rules reportFilterFilesRules `xml:"filter-rules"` } diff --git a/internal/http/services/owncloud/ocdav/spacelookup/spacelookup.go b/internal/http/services/owncloud/ocdav/spacelookup/spacelookup.go new file mode 100644 index 00000000000..3be319bec29 --- /dev/null +++ b/internal/http/services/owncloud/ocdav/spacelookup/spacelookup.go @@ -0,0 +1,153 @@ +// Copyright 2018-2022 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package spacelookup + +import ( + "context" + "fmt" + "strconv" + "strings" + + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + storageProvider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/cs3org/reva/pkg/utils" +) + +// LookUpStorageSpaceForPath returns: +// th storage spaces responsible for a path +// the status and error for the lookup +func LookUpStorageSpaceForPath(ctx context.Context, client gateway.GatewayAPIClient, path string) (*storageProvider.StorageSpace, *rpc.Status, error) { + // TODO add filter to only fetch spaces changed in the last 30 sec? + // TODO cache space information, invalidate after ... 5min? so we do not need to fetch all spaces? + // TODO use ListContainerStream to listen for changes + // retrieve a specific storage space + lSSReq := &storageProvider.ListStorageSpacesRequest{ + Opaque: &typesv1beta1.Opaque{ + Map: map[string]*typesv1beta1.OpaqueEntry{ + "path": { + Decoder: "plain", + Value: []byte(path), + }, + "unique": { + Decoder: "plain", + Value: []byte(strconv.FormatBool(true)), + }, + }, + }, + } + + lSSRes, err := client.ListStorageSpaces(ctx, lSSReq) + if err != nil || lSSRes.Status.Code != rpc.Code_CODE_OK { + return nil, lSSRes.Status, err + } + switch len(lSSRes.StorageSpaces) { + case 0: + return nil, status.NewNotFound(ctx, "no space found"), nil + case 1: + return lSSRes.StorageSpaces[0], lSSRes.Status, nil + } + + return nil, status.NewInternal(ctx, "too many spaces returned"), nil +} + +// LookUpStorageSpacesForPathWithChildren returns: +// the list of storage spaces responsible for a path +// the status and error for the lookup +func LookUpStorageSpacesForPathWithChildren(ctx context.Context, client gateway.GatewayAPIClient, path string) ([]*storageProvider.StorageSpace, *rpc.Status, error) { + // TODO add filter to only fetch spaces changed in the last 30 sec? + // TODO cache space information, invalidate after ... 5min? so we do not need to fetch all spaces? + // TODO use ListContainerStream to listen for changes + // retrieve a specific storage space + lSSReq := &storageProvider.ListStorageSpacesRequest{ + Opaque: &typesv1beta1.Opaque{ + Map: map[string]*typesv1beta1.OpaqueEntry{ + "path": {Decoder: "plain", Value: []byte(path)}, + "withChildMounts": {Decoder: "plain", Value: []byte("true")}, + }}, + } + + lSSRes, err := client.ListStorageSpaces(ctx, lSSReq) + if err != nil || lSSRes.Status.Code != rpc.Code_CODE_OK { + return nil, lSSRes.Status, err + } + + return lSSRes.StorageSpaces, lSSRes.Status, nil +} + +// LookUpStorageSpaceByID find a space by ID +func LookUpStorageSpaceByID(ctx context.Context, client gateway.GatewayAPIClient, spaceID string) (*storageProvider.StorageSpace, *rpc.Status, error) { + // retrieve a specific storage space + lSSReq := &storageProvider.ListStorageSpacesRequest{ + Opaque: &typesv1beta1.Opaque{}, + Filters: []*storageProvider.ListStorageSpacesRequest_Filter{ + { + Type: storageProvider.ListStorageSpacesRequest_Filter_TYPE_ID, + Term: &storageProvider.ListStorageSpacesRequest_Filter_Id{ + Id: &storageProvider.StorageSpaceId{ + OpaqueId: spaceID, + }, + }, + }, + }, + } + + lSSRes, err := client.ListStorageSpaces(ctx, lSSReq) + if err != nil || lSSRes.Status.Code != rpc.Code_CODE_OK { + return nil, lSSRes.Status, err + } + + switch len(lSSRes.StorageSpaces) { + case 0: + return nil, &rpc.Status{Code: rpc.Code_CODE_NOT_FOUND}, nil // since the caller only expects a single space return not found status + case 1: + return lSSRes.StorageSpaces[0], lSSRes.Status, nil + default: + return nil, nil, fmt.Errorf("unexpected number of spaces %d", len(lSSRes.StorageSpaces)) + } +} + +// LookUpStorageSpaceReference find a space by id and returns a relative reference +func LookUpStorageSpaceReference(ctx context.Context, client gateway.GatewayAPIClient, spaceID string, relativePath string, spacesDavRequest bool) (*storageProvider.Reference, *rpc.Status, error) { + space, status, err := LookUpStorageSpaceByID(ctx, client, spaceID) + if space == nil { + return nil, status, err + } + return MakeRelativeReference(space, relativePath, spacesDavRequest), status, err +} + +// MakeRelativeReference returns a relative reference for the given space and path +func MakeRelativeReference(space *storageProvider.StorageSpace, relativePath string, spacesDavRequest bool) *storageProvider.Reference { + if space.Opaque == nil || space.Opaque.Map == nil || space.Opaque.Map["path"] == nil || space.Opaque.Map["path"].Decoder != "plain" { + return nil // not mounted + } + spacePath := string(space.Opaque.Map["path"].Value) + relativeSpacePath := "." + if strings.HasPrefix(relativePath, spacePath) { + relativeSpacePath = utils.MakeRelativePath(strings.TrimPrefix(relativePath, spacePath)) + } else if spacesDavRequest { + relativeSpacePath = utils.MakeRelativePath(relativePath) + } + return &storageProvider.Reference{ + ResourceId: space.Root, + Path: relativeSpacePath, + } +} diff --git a/internal/http/services/owncloud/ocdav/spaces.go b/internal/http/services/owncloud/ocdav/spaces.go index 2f6276bcfb1..e633dc84771 100644 --- a/internal/http/services/owncloud/ocdav/spaces.go +++ b/internal/http/services/owncloud/ocdav/spaces.go @@ -19,19 +19,12 @@ package ocdav import ( - "context" - "fmt" "net/http" "path" - "strconv" - "strings" - rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - storageProvider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/propfind" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/pkg/rhttp/router" - "github.com/cs3org/reva/pkg/utils" ) // SpacesHandler handles trashbin requests @@ -50,6 +43,7 @@ func (h *SpacesHandler) init(c *Config) error { // Handler handles requests func (h *SpacesHandler) Handler(s *svc) http.Handler { + config := s.Config() return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // ctx := r.Context() // log := appctx.GetLogger(ctx) @@ -70,7 +64,10 @@ func (h *SpacesHandler) Handler(s *svc) http.Handler { switch r.Method { case MethodPropfind: - s.handleSpacesPropfind(w, r, spaceID) + p := propfind.NewHandler(config.PublicURL, func() (propfind.GatewayClient, error) { + return pool.GetGatewayServiceClient(config.GatewaySvc) + }) + p.HandleSpacesPropfind(w, r, spaceID) case MethodProppatch: s.handleSpacesProppatch(w, r, spaceID) case MethodLock: @@ -102,136 +99,3 @@ func (h *SpacesHandler) Handler(s *svc) http.Handler { } }) } - -// lookUpStorageSpacesForPath returns: -// th storage spaces responsible for a path -// the status and error for the lookup -func (s *svc) lookUpStorageSpaceForPath(ctx context.Context, path string) (*storageProvider.StorageSpace, *rpc.Status, error) { - // Get the getway client - gatewayClient, err := s.getClient() - if err != nil { - return nil, nil, err - } - - // TODO add filter to only fetch spaces changed in the last 30 sec? - // TODO cache space information, invalidate after ... 5min? so we do not need to fetch all spaces? - // TODO use ListContainerStream to listen for changes - // retrieve a specific storage space - lSSReq := &storageProvider.ListStorageSpacesRequest{ - Opaque: &typesv1beta1.Opaque{ - Map: map[string]*typesv1beta1.OpaqueEntry{ - "path": { - Decoder: "plain", - Value: []byte(path), - }, - "unique": { - Decoder: "plain", - Value: []byte(strconv.FormatBool(true)), - }, - }, - }, - } - - lSSRes, err := gatewayClient.ListStorageSpaces(ctx, lSSReq) - if err != nil || lSSRes.Status.Code != rpc.Code_CODE_OK { - return nil, lSSRes.Status, err - } - switch len(lSSRes.StorageSpaces) { - case 0: - return nil, status.NewNotFound(ctx, "no space found"), nil - case 1: - return lSSRes.StorageSpaces[0], lSSRes.Status, nil - } - - return nil, status.NewInternal(ctx, "too many spaces returned"), nil -} - -// lookUpStorageSpacesForPathWithChildren returns: -// the list of storage spaces responsible for a path -// the status and error for the lookup -func (s *svc) lookUpStorageSpacesForPathWithChildren(ctx context.Context, path string) ([]*storageProvider.StorageSpace, *rpc.Status, error) { - // Get the getway client - gatewayClient, err := s.getClient() - if err != nil { - return nil, nil, err - } - - // TODO add filter to only fetch spaces changed in the last 30 sec? - // TODO cache space information, invalidate after ... 5min? so we do not need to fetch all spaces? - // TODO use ListContainerStream to listen for changes - // retrieve a specific storage space - lSSReq := &storageProvider.ListStorageSpacesRequest{ - Opaque: &typesv1beta1.Opaque{ - Map: map[string]*typesv1beta1.OpaqueEntry{ - "path": {Decoder: "plain", Value: []byte(path)}, - "withChildMounts": {Decoder: "plain", Value: []byte("true")}, - }}, - } - - lSSRes, err := gatewayClient.ListStorageSpaces(ctx, lSSReq) - if err != nil || lSSRes.Status.Code != rpc.Code_CODE_OK { - return nil, lSSRes.Status, err - } - - return lSSRes.StorageSpaces, lSSRes.Status, nil -} -func (s *svc) lookUpStorageSpaceByID(ctx context.Context, spaceID string) (*storageProvider.StorageSpace, *rpc.Status, error) { - // Get the getway client - gatewayClient, err := s.getClient() - if err != nil { - return nil, nil, err - } - - // retrieve a specific storage space - lSSReq := &storageProvider.ListStorageSpacesRequest{ - Opaque: &typesv1beta1.Opaque{}, - Filters: []*storageProvider.ListStorageSpacesRequest_Filter{ - { - Type: storageProvider.ListStorageSpacesRequest_Filter_TYPE_ID, - Term: &storageProvider.ListStorageSpacesRequest_Filter_Id{ - Id: &storageProvider.StorageSpaceId{ - OpaqueId: spaceID, - }, - }, - }, - }, - } - - lSSRes, err := gatewayClient.ListStorageSpaces(ctx, lSSReq) - if err != nil || lSSRes.Status.Code != rpc.Code_CODE_OK { - return nil, lSSRes.Status, err - } - - switch len(lSSRes.StorageSpaces) { - case 0: - return nil, &rpc.Status{Code: rpc.Code_CODE_NOT_FOUND}, nil // since the caller only expects a single space return not found status - case 1: - return lSSRes.StorageSpaces[0], lSSRes.Status, nil - default: - return nil, nil, fmt.Errorf("unexpected number of spaces %d", len(lSSRes.StorageSpaces)) - } -} -func (s *svc) lookUpStorageSpaceReference(ctx context.Context, spaceID string, relativePath string, spacesDavRequest bool) (*storageProvider.Reference, *rpc.Status, error) { - space, status, err := s.lookUpStorageSpaceByID(ctx, spaceID) - if space == nil { - return nil, status, err - } - return makeRelativeReference(space, relativePath, spacesDavRequest), status, err -} - -func makeRelativeReference(space *storageProvider.StorageSpace, relativePath string, spacesDavRequest bool) *storageProvider.Reference { - if space.Opaque == nil || space.Opaque.Map == nil || space.Opaque.Map["path"] == nil || space.Opaque.Map["path"].Decoder != "plain" { - return nil // not mounted - } - spacePath := string(space.Opaque.Map["path"].Value) - relativeSpacePath := "." - if strings.HasPrefix(relativePath, spacePath) { - relativeSpacePath = utils.MakeRelativePath(strings.TrimPrefix(relativePath, spacePath)) - } else if spacesDavRequest { - relativeSpacePath = utils.MakeRelativePath(relativePath) - } - return &storageProvider.Reference{ - ResourceId: space.Root, - Path: relativeSpacePath, - } -} diff --git a/internal/http/services/owncloud/ocdav/trashbin.go b/internal/http/services/owncloud/ocdav/trashbin.go index 1e3787ecd81..dd58d7b22a3 100644 --- a/internal/http/services/owncloud/ocdav/trashbin.go +++ b/internal/http/services/owncloud/ocdav/trashbin.go @@ -29,6 +29,11 @@ import ( "strings" "time" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/propfind" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/props" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" rtrace "github.com/cs3org/reva/pkg/trace" "github.com/cs3org/reva/pkg/utils/resourceid" @@ -80,9 +85,7 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler { if u.Username != username { log.Debug().Str("username", username).Interface("user", u).Msg("trying to read another users trash") // listing other users trash is forbidden, no auth will change that - b, err := Marshal(exception{ - code: SabredavNotAuthenticated, - }) + b, err := errors.Marshal(errors.SabredavNotAuthenticated, "", "") if err != nil { log.Error().Msgf("error marshaling xml response: %s", b) w.WriteHeader(http.StatusInternalServerError) @@ -123,7 +126,7 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler { return } if getHomeRes.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(log, w, getHomeRes.Status) + errors.HandleErrorStatus(log, w, getHomeRes.Status) return } basePath = getHomeRes.Path @@ -135,9 +138,9 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler { } if key != "" && r.Method == MethodMove { // find path in url relative to trash base - trashBase := ctx.Value(ctxKeyBaseURI).(string) + trashBase := ctx.Value(net.CtxKeyBaseURI).(string) baseURI := path.Join(path.Dir(trashBase), "files", username) - ctx = context.WithValue(ctx, ctxKeyBaseURI, baseURI) + ctx = context.WithValue(ctx, net.CtxKeyBaseURI, baseURI) r = r.WithContext(ctx) // TODO make request.php optional in destination header @@ -167,12 +170,18 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s ctx, span := rtrace.Provider.Tracer("trash-bin").Start(r.Context(), "list_trashbin") defer span.End() - depth := r.Header.Get(HeaderDepth) + depth := r.Header.Get(net.HeaderDepth) if depth == "" { depth = "1" } sublog := appctx.GetLogger(ctx).With().Logger() + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } // see https://tools.ietf.org/html/rfc4918#section-9.1 if depth != "0" && depth != "1" && depth != "infinity" { @@ -188,8 +197,8 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s w.WriteHeader(http.StatusInternalServerError) return } - w.Header().Set(HeaderDav, "1, 3, extended-mkcol") - w.Header().Set(HeaderContentType, "application/xml; charset=utf-8") + w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol") + w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8") w.WriteHeader(http.StatusMultiStatus) _, err = w.Write([]byte(propRes)) if err != nil { @@ -199,7 +208,7 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s return } - pf, status, err := readPropfind(r.Body) + pf, status, err := propfind.ReadPropfind(r.Body) if err != nil { sublog.Debug().Err(err).Msg("error reading propfind request") w.WriteHeader(status) @@ -216,17 +225,17 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s return } - space, rpcstatus, err := s.lookUpStorageSpaceForPath(ctx, basePath) + space, rpcstatus, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, basePath) if err != nil { sublog.Error().Err(err).Str("path", basePath).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if rpcstatus.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, rpcstatus) + errors.HandleErrorStatus(&sublog, w, rpcstatus) return } - ref := makeRelativeReference(space, basePath, false) + ref := spacelookup.MakeRelativeReference(space, basePath, false) // ask gateway for recycle items getRecycleRes, err := gc.ListRecycle(ctx, &provider.ListRecycleRequest{Ref: ref, Key: path.Join(key, itemPath)}) @@ -238,7 +247,7 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s } if getRecycleRes.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, getRecycleRes.Status) + errors.HandleErrorStatus(&sublog, w, getRecycleRes.Status) return } @@ -265,7 +274,7 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s } if getRecycleRes.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, getRecycleRes.Status) + errors.HandleErrorStatus(&sublog, w, getRecycleRes.Status) return } items = append(items, getRecycleRes.RecycleItems...) @@ -293,8 +302,8 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s w.WriteHeader(http.StatusInternalServerError) return } - w.Header().Set(HeaderDav, "1, 3, extended-mkcol") - w.Header().Set(HeaderContentType, "application/xml; charset=utf-8") + w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol") + w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8") w.WriteHeader(http.StatusMultiStatus) _, err = w.Write([]byte(propRes)) if err != nil { @@ -303,25 +312,25 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s } } -func (h *TrashbinHandler) formatTrashPropfind(ctx context.Context, s *svc, u *userpb.User, pf *propfindXML, items []*provider.RecycleItem) (string, error) { - responses := make([]*responseXML, 0, len(items)+1) +func (h *TrashbinHandler) formatTrashPropfind(ctx context.Context, s *svc, u *userpb.User, pf *propfind.XML, items []*provider.RecycleItem) (string, error) { + responses := make([]*propfind.ResponseXML, 0, len(items)+1) // add trashbin dir . entry - responses = append(responses, &responseXML{ - Href: encodePath(ctx.Value(ctxKeyBaseURI).(string) + "/"), // url encode response.Href TODO - Propstat: []propstatXML{ + responses = append(responses, &propfind.ResponseXML{ + Href: net.EncodePath(ctx.Value(net.CtxKeyBaseURI).(string) + "/"), // url encode response.Href TODO + Propstat: []propfind.PropstatXML{ { Status: "HTTP/1.1 200 OK", - Prop: []*propertyXML{ - s.newPropRaw("d:resourcetype", ""), + Prop: []*props.PropertyXML{ + props.NewPropRaw("d:resourcetype", ""), }, }, { Status: "HTTP/1.1 404 Not Found", - Prop: []*propertyXML{ - s.newProp("oc:trashbin-original-filename", ""), - s.newProp("oc:trashbin-original-location", ""), - s.newProp("oc:trashbin-delete-datetime", ""), - s.newProp("d:getcontentlength", ""), + Prop: []*props.PropertyXML{ + props.NewProp("oc:trashbin-original-filename", ""), + props.NewProp("oc:trashbin-original-location", ""), + props.NewProp("oc:trashbin-delete-datetime", ""), + props.NewProp("d:getcontentlength", ""), }, }, }, @@ -348,17 +357,17 @@ func (h *TrashbinHandler) formatTrashPropfind(ctx context.Context, s *svc, u *us // itemToPropResponse needs to create a listing that contains a key and destination // the key is the name of an entry in the trash listing // for now we need to limit trash to the users home, so we can expect all trash keys to have the home storage as the opaque id -func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, u *userpb.User, pf *propfindXML, item *provider.RecycleItem) (*responseXML, error) { +func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, u *userpb.User, pf *propfind.XML, item *provider.RecycleItem) (*propfind.ResponseXML, error) { - baseURI := ctx.Value(ctxKeyBaseURI).(string) + baseURI := ctx.Value(net.CtxKeyBaseURI).(string) ref := path.Join(baseURI, u.Username, item.Key) if item.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { ref += "/" } - response := responseXML{ - Href: encodePath(ref), // url encode response.Href - Propstat: []propstatXML{}, + response := propfind.ResponseXML{ + Href: net.EncodePath(ref), // url encode response.Href + Propstat: []propfind.PropstatXML{}, } // TODO(jfd): if the path we list here is taken from the ListRecycle request we rely on the gateway to prefix it with the mount point @@ -369,86 +378,86 @@ func (h *TrashbinHandler) itemToPropResponse(ctx context.Context, s *svc, u *use // when allprops has been requested if pf.Allprop != nil { // return all known properties - response.Propstat = append(response.Propstat, propstatXML{ + response.Propstat = append(response.Propstat, propfind.PropstatXML{ Status: "HTTP/1.1 200 OK", - Prop: []*propertyXML{}, + Prop: []*props.PropertyXML{}, }) // yes this is redundant, can be derived from oc:trashbin-original-location which contains the full path, clients should not fetch it - response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-original-filename", path.Base(item.Ref.Path))) - response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-original-location", strings.TrimPrefix(item.Ref.Path, "/"))) - response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-delete-timestamp", strconv.FormatUint(item.DeletionTime.Seconds, 10))) - response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newProp("oc:trashbin-delete-datetime", dTime)) + response.Propstat[0].Prop = append(response.Propstat[0].Prop, props.NewProp("oc:trashbin-original-filename", path.Base(item.Ref.Path))) + response.Propstat[0].Prop = append(response.Propstat[0].Prop, props.NewProp("oc:trashbin-original-location", strings.TrimPrefix(item.Ref.Path, "/"))) + response.Propstat[0].Prop = append(response.Propstat[0].Prop, props.NewProp("oc:trashbin-delete-timestamp", strconv.FormatUint(item.DeletionTime.Seconds, 10))) + response.Propstat[0].Prop = append(response.Propstat[0].Prop, props.NewProp("oc:trashbin-delete-datetime", dTime)) if item.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - response.Propstat[0].Prop = append(response.Propstat[0].Prop, s.newPropRaw("d:resourcetype", "")) + response.Propstat[0].Prop = append(response.Propstat[0].Prop, props.NewPropRaw("d:resourcetype", "")) // TODO(jfd): decide if we can and want to list oc:size for folders } else { response.Propstat[0].Prop = append(response.Propstat[0].Prop, - s.newProp("d:resourcetype", ""), - s.newProp("d:getcontentlength", fmt.Sprintf("%d", item.Size)), + props.NewProp("d:resourcetype", ""), + props.NewProp("d:getcontentlength", fmt.Sprintf("%d", item.Size)), ) } } else { // otherwise return only the requested properties - propstatOK := propstatXML{ + propstatOK := propfind.PropstatXML{ Status: "HTTP/1.1 200 OK", - Prop: []*propertyXML{}, + Prop: []*props.PropertyXML{}, } - propstatNotFound := propstatXML{ + propstatNotFound := propfind.PropstatXML{ Status: "HTTP/1.1 404 Not Found", - Prop: []*propertyXML{}, + Prop: []*props.PropertyXML{}, } size := fmt.Sprintf("%d", item.Size) for i := range pf.Prop { switch pf.Prop[i].Space { - case _nsOwncloud: + case net.NsOwncloud: switch pf.Prop[i].Local { case "oc:size": if item.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getcontentlength", size)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getcontentlength", size)) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:size", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:size", "")) } case "trashbin-original-filename": // yes this is redundant, can be derived from oc:trashbin-original-location which contains the full path, clients should not fetch it - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:trashbin-original-filename", path.Base(item.Ref.Path))) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:trashbin-original-filename", path.Base(item.Ref.Path))) case "trashbin-original-location": // TODO (jfd) double check and clarify the cs3 spec what the Key is about and if Path is only the folder that contains the file or if it includes the filename - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:trashbin-original-location", strings.TrimPrefix(item.Ref.Path, "/"))) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:trashbin-original-location", strings.TrimPrefix(item.Ref.Path, "/"))) case "trashbin-delete-datetime": - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:trashbin-delete-datetime", dTime)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:trashbin-delete-datetime", dTime)) case "trashbin-delete-timestamp": - propstatOK.Prop = append(propstatOK.Prop, s.newProp("oc:trashbin-delete-timestamp", strconv.FormatUint(item.DeletionTime.Seconds, 10))) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:trashbin-delete-timestamp", strconv.FormatUint(item.DeletionTime.Seconds, 10))) default: - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("oc:"+pf.Prop[i].Local, "")) } - case _nsDav: + case net.NsDav: switch pf.Prop[i].Local { case "getcontentlength": if item.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:getcontentlength", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:getcontentlength", "")) } else { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getcontentlength", size)) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getcontentlength", size)) } case "resourcetype": if item.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("d:resourcetype", "")) + propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("d:resourcetype", "")) } else { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:resourcetype", "")) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:resourcetype", "")) // redirectref is another option } case "getcontenttype": if item.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - propstatOK.Prop = append(propstatOK.Prop, s.newProp("d:getcontenttype", "httpd/unix-directory")) + propstatOK.Prop = append(propstatOK.Prop, props.NewProp("d:getcontenttype", "httpd/unix-directory")) } else { - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:getcontenttype", "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:getcontenttype", "")) } default: - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("d:"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:"+pf.Prop[i].Local, "")) } default: // TODO (jfd) lookup shortname for unknown namespaces? - propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp(pf.Prop[i].Space+":"+pf.Prop[i].Local, "")) + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp(pf.Prop[i].Space+":"+pf.Prop[i].Local, "")) } } response.Propstat = append(response.Propstat, propstatOK, propstatNotFound) @@ -463,7 +472,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc sublog := appctx.GetLogger(ctx).With().Logger() - overwrite := r.Header.Get(HeaderOverwrite) + overwrite := r.Header.Get(net.HeaderOverwrite) overwrite = strings.ToUpper(overwrite) if overwrite == "" { @@ -481,17 +490,17 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc w.WriteHeader(http.StatusInternalServerError) return } - space, rpcstatus, err := s.lookUpStorageSpaceForPath(ctx, dst) + space, rpcstatus, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, dst) if err != nil { sublog.Error().Err(err).Str("path", basePath).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if rpcstatus.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, rpcstatus) + errors.HandleErrorStatus(&sublog, w, rpcstatus) return } - dstRef := makeRelativeReference(space, dst, false) + dstRef := spacelookup.MakeRelativeReference(space, dst, false) dstStatReq := &provider.StatRequest{ Ref: dstRef, @@ -505,7 +514,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc } if dstStatRes.Status.Code != rpc.Code_CODE_OK && dstStatRes.Status.Code != rpc.Code_CODE_NOT_FOUND { - HandleErrorStatus(&sublog, w, dstStatRes.Status) + errors.HandleErrorStatus(&sublog, w, dstStatRes.Status) return } @@ -513,7 +522,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc // restore location exists, and if it doesn't returns a conflict error code. if dstStatRes.Status.Code == rpc.Code_CODE_NOT_FOUND && isNested(dst) { parentStatReq := &provider.StatRequest{ - Ref: makeRelativeReference(space, filepath.Dir(dst), false), + Ref: spacelookup.MakeRelativeReference(space, filepath.Dir(dst), false), } parentStatResponse, err := client.Stat(ctx, parentStatReq) @@ -524,7 +533,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc } if parentStatResponse.Status.Code == rpc.Code_CODE_NOT_FOUND { - HandleErrorStatus(&sublog, w, &rpc.Status{Code: rpc.Code_CODE_FAILED_PRECONDITION}) + errors.HandleErrorStatus(&sublog, w, &rpc.Status{Code: rpc.Code_CODE_FAILED_PRECONDITION}) return } } @@ -536,12 +545,12 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc if overwrite != "T" { sublog.Warn().Str("overwrite", overwrite).Msg("dst already exists") w.WriteHeader(http.StatusPreconditionFailed) // 412, see https://tools.ietf.org/html/rfc4918#section-9.9.4 - b, err := Marshal(exception{ - code: SabredavPreconditionFailed, - message: "The destination node already exists, and the overwrite header is set to false", - header: HeaderOverwrite, - }) - HandleWebdavError(&sublog, w, b, err) + b, err := errors.Marshal( + errors.SabredavPreconditionFailed, + "The destination node already exists, and the overwrite header is set to false", + net.HeaderOverwrite, + ) + errors.HandleWebdavError(&sublog, w, b, err) return } // delete existing tree @@ -554,19 +563,19 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc } if delRes.Status.Code != rpc.Code_CODE_OK && delRes.Status.Code != rpc.Code_CODE_NOT_FOUND { - HandleErrorStatus(&sublog, w, delRes.Status) + errors.HandleErrorStatus(&sublog, w, delRes.Status) return } } - sourceSpace, rpcstatus, err := s.lookUpStorageSpaceForPath(ctx, basePath) + sourceSpace, rpcstatus, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, basePath) if err != nil { sublog.Error().Err(err).Str("path", basePath).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if rpcstatus.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, rpcstatus) + errors.HandleErrorStatus(&sublog, w, rpcstatus) return } req := &provider.RestoreRecycleItemRequest{ @@ -574,7 +583,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc // this means we can only undelete on the same storage, not to a different folder // use the key which is prefixed with the StoragePath to lookup the correct storage ... // TODO currently limited to the home storage - Ref: makeRelativeReference(sourceSpace, basePath, false), + Ref: spacelookup.MakeRelativeReference(sourceSpace, basePath, false), Key: path.Join(key, itemPath), RestoreRef: dstRef, } @@ -589,13 +598,10 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc if res.Status.Code != rpc.Code_CODE_OK { if res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { w.WriteHeader(http.StatusForbidden) - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: "Permission denied to restore", - }) - HandleWebdavError(&sublog, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, "Permission denied to restore", "") + errors.HandleWebdavError(&sublog, w, b, err) } - HandleErrorStatus(&sublog, w, res.Status) + errors.HandleErrorStatus(&sublog, w, res.Status) return } @@ -606,15 +612,15 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc return } if dstStatRes.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, dstStatRes.Status) + errors.HandleErrorStatus(&sublog, w, dstStatRes.Status) return } info := dstStatRes.Info - w.Header().Set(HeaderContentType, info.MimeType) - w.Header().Set(HeaderETag, info.Etag) - w.Header().Set(HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) - w.Header().Set(HeaderOCETag, info.Etag) + w.Header().Set(net.HeaderContentType, info.MimeType) + w.Header().Set(net.HeaderETag, info.Etag) + w.Header().Set(net.HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) + w.Header().Set(net.HeaderOCETag, info.Etag) w.WriteHeader(successCode) } @@ -635,19 +641,19 @@ func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, // set key as opaque id, the storageprovider will use it as the key for the // storage drives PurgeRecycleItem key call - space, status, err := s.lookUpStorageSpaceForPath(ctx, basePath) + space, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, basePath) if err != nil { sublog.Error().Err(err).Str("path", basePath).Msg("failed to look up storage space") w.WriteHeader(http.StatusInternalServerError) return } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } req := &provider.PurgeRecycleRequest{ - Ref: makeRelativeReference(space, basePath, false), + Ref: spacelookup.MakeRelativeReference(space, basePath, false), Key: path.Join(key, itemPath), } @@ -664,11 +670,8 @@ func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, sublog.Debug().Str("path", basePath).Str("key", key).Interface("status", res.Status).Msg("resource not found") w.WriteHeader(http.StatusConflict) m := fmt.Sprintf("path %s not found", basePath) - b, err := Marshal(exception{ - code: SabredavConflict, - message: m, - }) - HandleWebdavError(&sublog, w, b, err) + b, err := errors.Marshal(errors.SabredavConflict, m, "") + errors.HandleWebdavError(&sublog, w, b, err) case rpc.Code_CODE_PERMISSION_DENIED: w.WriteHeader(http.StatusForbidden) var m string @@ -677,13 +680,10 @@ func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, } else { m = "Permission denied to delete" } - b, err := Marshal(exception{ - code: SabredavPermissionDenied, - message: m, - }) - HandleWebdavError(&sublog, w, b, err) + b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + errors.HandleWebdavError(&sublog, w, b, err) default: - HandleErrorStatus(&sublog, w, res.Status) + errors.HandleErrorStatus(&sublog, w, res.Status) } } diff --git a/internal/http/services/owncloud/ocdav/tus.go b/internal/http/services/owncloud/ocdav/tus.go index f5ddb67ec8c..7d48af06d02 100644 --- a/internal/http/services/owncloud/ocdav/tus.go +++ b/internal/http/services/owncloud/ocdav/tus.go @@ -31,6 +31,9 @@ import ( link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rhttp" @@ -46,7 +49,7 @@ func (s *svc) handlePathTusPost(w http.ResponseWriter, r *http.Request, ns strin defer span.End() // read filename from metadata - meta := tusd.ParseMetadataHeader(r.Header.Get(HeaderUploadMetadata)) + meta := tusd.ParseMetadataHeader(r.Header.Get(net.HeaderUploadMetadata)) for _, r := range nameRules { if !r.Test(meta["filename"]) { w.WriteHeader(http.StatusPreconditionFailed) @@ -72,22 +75,28 @@ func (s *svc) handleSpacesTusPost(w http.ResponseWriter, r *http.Request, spaceI defer span.End() // read filename from metadata - meta := tusd.ParseMetadataHeader(r.Header.Get(HeaderUploadMetadata)) + meta := tusd.ParseMetadataHeader(r.Header.Get(net.HeaderUploadMetadata)) if meta["filename"] == "" { w.WriteHeader(http.StatusPreconditionFailed) return } sublog := appctx.GetLogger(ctx).With().Str("spaceid", spaceID).Str("path", r.URL.Path).Logger() + client, err := s.getClient() + if err != nil { + sublog.Error().Err(err).Msg("error getting grpc client") + w.WriteHeader(http.StatusInternalServerError) + return + } - spaceRef, status, err := s.lookUpStorageSpaceReference(ctx, spaceID, path.Join(r.URL.Path, meta["filename"]), true) + spaceRef, status, err := spacelookup.LookUpStorageSpaceReference(ctx, client, spaceID, path.Join(r.URL.Path, meta["filename"]), true) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) return } if status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, status) + errors.HandleErrorStatus(&sublog, w, status) return } @@ -95,20 +104,20 @@ func (s *svc) handleSpacesTusPost(w http.ResponseWriter, r *http.Request, spaceI } func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http.Request, meta map[string]string, ref *provider.Reference, log zerolog.Logger) { - w.Header().Add(HeaderAccessControlAllowHeaders, strings.Join([]string{HeaderTusResumable, HeaderUploadLength, HeaderUploadMetadata, HeaderIfMatch}, ", ")) - w.Header().Add(HeaderAccessControlExposeHeaders, strings.Join([]string{HeaderTusResumable, HeaderLocation}, ", ")) - w.Header().Set(HeaderTusExtension, "creation,creation-with-upload,checksum,expiration") + w.Header().Add(net.HeaderAccessControlAllowHeaders, strings.Join([]string{net.HeaderTusResumable, net.HeaderUploadLength, net.HeaderUploadMetadata, net.HeaderIfMatch}, ", ")) + w.Header().Add(net.HeaderAccessControlExposeHeaders, strings.Join([]string{net.HeaderTusResumable, net.HeaderLocation}, ", ")) + w.Header().Set(net.HeaderTusExtension, "creation,creation-with-upload,checksum,expiration") - w.Header().Set(HeaderTusResumable, "1.0.0") + w.Header().Set(net.HeaderTusResumable, "1.0.0") // Test if the version sent by the client is supported // GET methods are not checked since a browser may visit this URL and does // not include this header. This request is not part of the specification. - if r.Header.Get(HeaderTusResumable) != "1.0.0" { + if r.Header.Get(net.HeaderTusResumable) != "1.0.0" { w.WriteHeader(http.StatusPreconditionFailed) return } - if r.Header.Get(HeaderUploadLength) == "" { + if r.Header.Get(net.HeaderUploadLength) == "" { w.WriteHeader(http.StatusPreconditionFailed) return } @@ -137,7 +146,7 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. } if sRes.Status.Code != rpc.Code_CODE_OK && sRes.Status.Code != rpc.Code_CODE_NOT_FOUND { - HandleErrorStatus(&log, w, sRes.Status) + errors.HandleErrorStatus(&log, w, sRes.Status) return } @@ -149,7 +158,7 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. } if info != nil { - clientETag := r.Header.Get(HeaderIfMatch) + clientETag := r.Header.Get(net.HeaderIfMatch) serverETag := info.Etag if clientETag != "" { if clientETag != serverETag { @@ -161,15 +170,15 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. } opaqueMap := map[string]*typespb.OpaqueEntry{ - HeaderUploadLength: { + net.HeaderUploadLength: { Decoder: "plain", - Value: []byte(r.Header.Get(HeaderUploadLength)), + Value: []byte(r.Header.Get(net.HeaderUploadLength)), }, } mtime := meta["mtime"] if mtime != "" { - opaqueMap[HeaderOCMtime] = &typespb.OpaqueEntry{ + opaqueMap[net.HeaderOCMtime] = &typespb.OpaqueEntry{ Decoder: "plain", Value: []byte(mtime), } @@ -195,7 +204,7 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. w.WriteHeader(http.StatusPreconditionFailed) return } - HandleErrorStatus(&log, w, uRes.Status) + errors.HandleErrorStatus(&log, w, uRes.Status) return } @@ -215,13 +224,13 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. ep += token } - w.Header().Set(HeaderLocation, ep) + w.Header().Set(net.HeaderLocation, ep) // for creation-with-upload extension forward bytes to dataprovider // TODO check this really streams - if r.Header.Get(HeaderContentType) == "application/offset+octet-stream" { + if r.Header.Get(net.HeaderContentType) == "application/offset+octet-stream" { - length, err := strconv.ParseInt(r.Header.Get(HeaderContentLength), 10, 64) + length, err := strconv.ParseInt(r.Header.Get(net.HeaderContentLength), 10, 64) if err != nil { log.Debug().Err(err).Msg("wrong request") w.WriteHeader(http.StatusBadRequest) @@ -237,14 +246,14 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. return } - httpReq.Header.Set(HeaderContentType, r.Header.Get(HeaderContentType)) - httpReq.Header.Set(HeaderContentLength, r.Header.Get(HeaderContentLength)) - if r.Header.Get(HeaderUploadOffset) != "" { - httpReq.Header.Set(HeaderUploadOffset, r.Header.Get(HeaderUploadOffset)) + httpReq.Header.Set(net.HeaderContentType, r.Header.Get(net.HeaderContentType)) + httpReq.Header.Set(net.HeaderContentLength, r.Header.Get(net.HeaderContentLength)) + if r.Header.Get(net.HeaderUploadOffset) != "" { + httpReq.Header.Set(net.HeaderUploadOffset, r.Header.Get(net.HeaderUploadOffset)) } else { - httpReq.Header.Set(HeaderUploadOffset, "0") + httpReq.Header.Set(net.HeaderUploadOffset, "0") } - httpReq.Header.Set(HeaderTusResumable, r.Header.Get(HeaderTusResumable)) + httpReq.Header.Set(net.HeaderTusResumable, r.Header.Get(net.HeaderTusResumable)) httpRes, err = s.client.Do(httpReq) if err != nil { @@ -254,16 +263,16 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. } defer httpRes.Body.Close() - w.Header().Set(HeaderUploadOffset, httpRes.Header.Get(HeaderUploadOffset)) - w.Header().Set(HeaderTusResumable, httpRes.Header.Get(HeaderTusResumable)) - w.Header().Set(HeaderTusUploadExpires, httpRes.Header.Get(HeaderTusUploadExpires)) + w.Header().Set(net.HeaderUploadOffset, httpRes.Header.Get(net.HeaderUploadOffset)) + w.Header().Set(net.HeaderTusResumable, httpRes.Header.Get(net.HeaderTusResumable)) + w.Header().Set(net.HeaderTusUploadExpires, httpRes.Header.Get(net.HeaderTusUploadExpires)) if httpRes.StatusCode != http.StatusNoContent { w.WriteHeader(httpRes.StatusCode) return } // check if upload was fully completed - if length == 0 || httpRes.Header.Get(HeaderUploadOffset) == r.Header.Get(HeaderUploadLength) { + if length == 0 || httpRes.Header.Get(net.HeaderUploadOffset) == r.Header.Get(net.HeaderUploadLength) { // get uploaded file metadata sRes, err := client.Stat(ctx, sReq) @@ -283,7 +292,7 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. return } - HandleErrorStatus(&log, w, sRes.Status) + errors.HandleErrorStatus(&log, w, sRes.Status) return } @@ -293,9 +302,9 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. w.WriteHeader(http.StatusInternalServerError) return } - if httpRes != nil && httpRes.Header != nil && httpRes.Header.Get(HeaderOCMtime) != "" { + if httpRes != nil && httpRes.Header != nil && httpRes.Header.Get(net.HeaderOCMtime) != "" { // set the "accepted" value if returned in the upload response headers - w.Header().Set(HeaderOCMtime, httpRes.Header.Get(HeaderOCMtime)) + w.Header().Set(net.HeaderOCMtime, httpRes.Header.Get(net.HeaderOCMtime)) } // get WebDav permissions for file @@ -307,7 +316,7 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. isPublic = ls != nil } } - isShared := !isCurrentUserOwner(ctx, info.Owner) + isShared := !net.IsCurrentUserOwner(ctx, info.Owner) role := conversions.RoleFromResourcePermissions(info.PermissionSet) permissions := role.WebDAVPermissions( info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER, @@ -316,15 +325,15 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. isPublic, ) - w.Header().Set(HeaderContentType, info.MimeType) - w.Header().Set(HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) - w.Header().Set(HeaderOCETag, info.Etag) - w.Header().Set(HeaderETag, info.Etag) - w.Header().Set(HeaderOCPermissions, permissions) + w.Header().Set(net.HeaderContentType, info.MimeType) + w.Header().Set(net.HeaderOCFileID, resourceid.OwnCloudResourceIDWrap(info.Id)) + w.Header().Set(net.HeaderOCETag, info.Etag) + w.Header().Set(net.HeaderETag, info.Etag) + w.Header().Set(net.HeaderOCPermissions, permissions) t := utils.TSToTime(info.Mtime).UTC() lastModifiedString := t.Format(time.RFC1123Z) - w.Header().Set(HeaderLastModified, lastModifiedString) + w.Header().Set(net.HeaderLastModified, lastModifiedString) } } diff --git a/internal/http/services/owncloud/ocdav/versions.go b/internal/http/services/owncloud/ocdav/versions.go index f56bba6e480..ebe609a925d 100644 --- a/internal/http/services/owncloud/ocdav/versions.go +++ b/internal/http/services/owncloud/ocdav/versions.go @@ -23,6 +23,9 @@ import ( "net/http" "path" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/propfind" rtrace "github.com/cs3org/reva/pkg/trace" "github.com/cs3org/reva/pkg/utils/resourceid" @@ -54,8 +57,8 @@ func (h *VersionsHandler) Handler(s *svc, rid *provider.ResourceId) http.Handler } // baseURI is encoded as part of the response payload in href field - baseURI := path.Join(ctx.Value(ctxKeyBaseURI).(string), resourceid.OwnCloudResourceIDWrap(rid)) - ctx = context.WithValue(ctx, ctxKeyBaseURI, baseURI) + baseURI := path.Join(ctx.Value(net.CtxKeyBaseURI).(string), resourceid.OwnCloudResourceIDWrap(rid)) + ctx = context.WithValue(ctx, net.CtxKeyBaseURI, baseURI) r = r.WithContext(ctx) var key string @@ -86,7 +89,7 @@ func (h *VersionsHandler) doListVersions(w http.ResponseWriter, r *http.Request, sublog := appctx.GetLogger(ctx).With().Interface("resourceid", rid).Logger() - pf, status, err := readPropfind(r.Body) + pf, status, err := propfind.ReadPropfind(r.Body) if err != nil { sublog.Debug().Err(err).Msg("error reading propfind request") w.WriteHeader(status) @@ -110,14 +113,11 @@ func (h *VersionsHandler) doListVersions(w http.ResponseWriter, r *http.Request, if res.Status.Code != rpc.Code_CODE_OK { if res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED || res.Status.Code == rpc.Code_CODE_NOT_FOUND { w.WriteHeader(http.StatusNotFound) - b, err := Marshal(exception{ - code: SabredavNotFound, - message: "Resource not found", - }) - HandleWebdavError(&sublog, w, b, err) + b, err := errors.Marshal(errors.SabredavNotFound, "Resource not found", "") + errors.HandleWebdavError(&sublog, w, b, err) return } - HandleErrorStatus(&sublog, w, res.Status) + errors.HandleErrorStatus(&sublog, w, res.Status) return } @@ -130,7 +130,7 @@ func (h *VersionsHandler) doListVersions(w http.ResponseWriter, r *http.Request, return } if lvRes.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, lvRes.Status) + errors.HandleErrorStatus(&sublog, w, lvRes.Status) return } @@ -165,14 +165,14 @@ func (h *VersionsHandler) doListVersions(w http.ResponseWriter, r *http.Request, infos = append(infos, vi) } - propRes, err := s.multistatusResponse(ctx, &pf, infos, "", nil) + propRes, err := propfind.MultistatusResponse(ctx, &pf, infos, s.c.PublicURL, "", nil) if err != nil { sublog.Error().Err(err).Msg("error formatting propfind") w.WriteHeader(http.StatusInternalServerError) return } - w.Header().Set(HeaderDav, "1, 3, extended-mkcol") - w.Header().Set(HeaderContentType, "application/xml; charset=utf-8") + w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol") + w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8") w.WriteHeader(http.StatusMultiStatus) _, err = w.Write([]byte(propRes)) if err != nil { @@ -207,7 +207,7 @@ func (h *VersionsHandler) doRestore(w http.ResponseWriter, r *http.Request, s *s return } if res.Status.Code != rpc.Code_CODE_OK { - HandleErrorStatus(&sublog, w, res.Status) + errors.HandleErrorStatus(&sublog, w, res.Status) return } w.WriteHeader(http.StatusNoContent) diff --git a/internal/http/services/owncloud/ocdav/webdav.go b/internal/http/services/owncloud/ocdav/webdav.go index abd984b9fff..fcb24b6a175 100644 --- a/internal/http/services/owncloud/ocdav/webdav.go +++ b/internal/http/services/owncloud/ocdav/webdav.go @@ -23,7 +23,10 @@ import ( "net/http" "path" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/propfind" "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" ) // Common Webdav methods. @@ -40,45 +43,6 @@ const ( MethodReport = "REPORT" ) -// Common HTTP headers. -const ( - HeaderAcceptRanges = "Accept-Ranges" - HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers" - HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers" - HeaderContentDisposistion = "Content-Disposition" - HeaderContentLength = "Content-Length" - HeaderContentRange = "Content-Range" - HeaderContentType = "Content-Type" - HeaderETag = "ETag" - HeaderLastModified = "Last-Modified" - HeaderLocation = "Location" - HeaderRange = "Range" - HeaderIfMatch = "If-Match" -) - -// Non standard HTTP headers. -const ( - HeaderOCFileID = "OC-FileId" - HeaderOCETag = "OC-ETag" - HeaderOCChecksum = "OC-Checksum" - HeaderOCPermissions = "OC-Perm" - HeaderDepth = "Depth" - HeaderDav = "DAV" - HeaderTusResumable = "Tus-Resumable" - HeaderTusVersion = "Tus-Version" - HeaderTusExtension = "Tus-Extension" - HeaderTusChecksumAlgorithm = "Tus-Checksum-Algorithm" - HeaderTusUploadExpires = "Upload-Expires" - HeaderDestination = "Destination" - HeaderOverwrite = "Overwrite" - HeaderUploadChecksum = "Upload-Checksum" - HeaderUploadLength = "Upload-Length" - HeaderUploadMetadata = "Upload-Metadata" - HeaderUploadOffset = "Upload-Offset" - HeaderOCMtime = "X-OC-Mtime" - HeaderExpectedEntityLength = "X-Expected-Entity-Length" -) - // WebDavHandler implements a dav endpoint type WebDavHandler struct { namespace string @@ -93,20 +57,21 @@ func (h *WebDavHandler) init(ns string, useLoggedInUserNS bool) error { // Handler handles requests func (h *WebDavHandler) Handler(s *svc) http.Handler { + config := s.Config() return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ns, newPath, err := s.ApplyLayout(r.Context(), h.namespace, h.useLoggedInUserNS, r.URL.Path) if err != nil { w.WriteHeader(http.StatusNotFound) - b, err := Marshal(exception{ - code: SabredavNotFound, - message: fmt.Sprintf("could not get storage for %s", r.URL.Path), - }) - HandleWebdavError(appctx.GetLogger(r.Context()), w, b, err) + b, err := errors.Marshal(errors.SabredavNotFound, fmt.Sprintf("could not get storage for %s", r.URL.Path), "") + errors.HandleWebdavError(appctx.GetLogger(r.Context()), w, b, err) } r.URL.Path = newPath switch r.Method { case MethodPropfind: - s.handlePathPropfind(w, r, ns) + p := propfind.NewHandler(config.PublicURL, func() (propfind.GatewayClient, error) { + return pool.GetGatewayServiceClient(config.GatewaySvc) + }) + p.HandlePathPropfind(w, r, ns) case MethodLock: s.handleLock(w, r, ns) case MethodUnlock: diff --git a/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go b/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go index 5bcf066cdac..8a0214cd18f 100644 --- a/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go +++ b/internal/http/services/owncloud/ocs/handlers/cloud/users/users.go @@ -26,7 +26,7 @@ import ( userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/internal/http/services/owncloud/ocdav" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/config" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" "github.com/cs3org/reva/internal/http/services/owncloud/ocs/response" @@ -126,7 +126,7 @@ func (h *Handler) GetUsers(w http.ResponseWriter, r *http.Request) { return } if res.Status.Code != rpc.Code_CODE_OK { - ocdav.HandleErrorStatus(sublog, w, res.Status) + errors.HandleErrorStatus(sublog, w, res.Status) return } @@ -152,7 +152,7 @@ func (h *Handler) GetUsers(w http.ResponseWriter, r *http.Request) { } if getQuotaRes.Status.Code != rpc.Code_CODE_OK { - ocdav.HandleErrorStatus(sublog, w, getQuotaRes.Status) + errors.HandleErrorStatus(sublog, w, getQuotaRes.Status) return } total = getQuotaRes.TotalBytes From 02e8152c8b70a44048b2b4fc3a9ee7f15f77d643 Mon Sep 17 00:00:00 2001 From: kobergj Date: Wed, 12 Jan 2022 16:57:28 +0100 Subject: [PATCH 04/49] [tests-only] Merge master into edge (#2435) * [Build-deps] Additional rules for CODEOWNERS (#2323) * Remove share refs from trashbin (#2298) * Public link propfind (#2315) * fix public share type in propfinds (#2316) * Bump core commit id for tests (#2331) * Revert "Fix content disposition (#2303)" (#2332) This reverts commit 3cba22371b78213f2e49197c2783220331a264bd. * [Build-deps]: Bump github.com/gomodule/redigo from 1.8.5 to 1.8.6 (#2326) * [Build-deps]: Bump github.com/mitchellh/mapstructure from 1.4.2 to 1.4.3 (#2324) * [Build-deps]: Bump github.com/aws/aws-sdk-go from 1.42.9 to 1.42.19 (#2325) * fix app provider new file action and improve app provider error codes (#2210) * Parse URL path to determine file name (#2346) * v1.17.0 * handle non existent spaces gracefully (#2354) * Bump core commit id for tests (#2365) * [Build-deps]: Bump github.com/minio/minio-go/v7 from 7.0.16 to 7.0.18 (#2363) * [Build-deps]: Bump github.com/ReneKroon/ttlcache/v2 from 2.9.0 to 2.10.0 (#2358) * [Build-deps]: Bump go.opentelemetry.io/otel/exporters/jaeger (#2362) * fix tests by pointing to the right owncloud/core commit id for tests (#2375) * add new file capabilties to ocs for the app provider (#2379) * Remove test from expected to fail and bump commit id (#2380) * add .drone.env to CODEOWNERS as it is part of the test files (#2378) * fix webdav copy for zero byte files (#2374) * Implement touch file (#2369) * implement cs3org/cs3apis#154 * use TouchFile for the app provider * add changelog and comments * revert use TouchFile in app provider * fix resource typo Co-authored-by: Giuseppe Lo Presti Co-authored-by: Giuseppe Lo Presti * Dummy implementation of the Lock CS3APIs (#2350) * allow new file create with app provider on public links (#2385) * Bump core commit id and use core master for tests (#2391) * Add product to ocs Version struct (#2397) The web ui will announce the backend version in the javascript console and is supposed to include the product name as well. The version seems to be a good location for the product field as it already includes the software edition as well. * bump core commit id for tests (#2404) * [Build-deps]: Bump github.com/mattn/go-sqlite3 from 1.14.9 to 1.14.10 (#2409) * [Build-deps]: Bump github.com/minio/minio-go/v7 from 7.0.18 to 7.0.20 (#2408) * [Build-deps]: Bump github.com/rs/cors from 1.8.0 to 1.8.2 (#2399) * [Build-deps]: Bump github.com/ReneKroon/ttlcache/v2 (#2387) * [Build-deps]: Bump go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc (#2359) * [tests-only] format .drone.star (#2411) * update tus/tusd to version 1.8.0 (#2393) * Fixes for apps in public shares, project spaces for EOS driver (#2371) * [Build-deps]: Bump github.com/aws/aws-sdk-go from 1.42.19 to 1.42.27 (#2414) * [Build-deps]: Bump github.com/rs/zerolog from 1.26.0 to 1.26.1 (#2388) * update owncloud core commit id (#2418) * [Build-deps]: Bump github.com/mattn/go-sqlite3 (#2425) * [Build-deps]: Bump github.com/gomodule/redigo from 1.8.6 to 1.8.8 (#2426) * OIDC and WOPI changes for lightweight users (#2278) * don't create references in gateway Signed-off-by: jkoberg * don't run virtual views testsuite Signed-off-by: jkoberg * bring back token scope expanding Signed-off-by: jkoberg Co-authored-by: Giuseppe Lo Presti Co-authored-by: Gianmaria Del Monte <39946305+gmgigi96@users.noreply.github.com> Co-authored-by: David Christofas Co-authored-by: Swikriti Tripathi <41103328+SwikritiT@users.noreply.github.com> Co-authored-by: Ishank Arora Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Willy Kloucek <34452982+wkloucek@users.noreply.github.com> Co-authored-by: Michael Barz Co-authored-by: Phil Davis Co-authored-by: Benedikt Kulmann Co-authored-by: Saw-jan Gurung Co-authored-by: PKiran <39373750+kiranparajuli589@users.noreply.github.com> --- changelog/unreleased/oidc-lw-users.md | 3 + go.mod | 4 +- go.sum | 10 +-- internal/grpc/interceptors/auth/scope.go | 3 + .../grpc/services/gateway/authprovider.go | 2 + .../grpc/services/gateway/storageprovider.go | 74 ++++++++++--------- .../services/gateway/usershareprovider.go | 55 +++++++++++++- pkg/app/provider/wopi/wopi.go | 6 ++ pkg/auth/manager/oidc/oidc.go | 23 +++++- pkg/auth/scope/lightweight.go | 32 +++++++- pkg/auth/scope/receivedshare.go | 5 +- pkg/auth/scope/share.go | 5 +- pkg/cbox/user/rest/rest.go | 47 ++++++++++-- pkg/eosclient/eosbinary/eosbinary.go | 2 +- pkg/storage/utils/acl/acl.go | 26 ++++++- pkg/storage/utils/eosfs/eosfs.go | 15 +++- 16 files changed, 250 insertions(+), 62 deletions(-) create mode 100644 changelog/unreleased/oidc-lw-users.md diff --git a/changelog/unreleased/oidc-lw-users.md b/changelog/unreleased/oidc-lw-users.md new file mode 100644 index 00000000000..2053f695513 --- /dev/null +++ b/changelog/unreleased/oidc-lw-users.md @@ -0,0 +1,3 @@ +Enhancement: OIDC driver changes for lightweight users + +https://github.com/cs3org/reva/pull/2278 \ No newline at end of file diff --git a/go.mod b/go.mod index 38b1fae6b9e..02be6ef2f59 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/go-sql-driver/mysql v1.6.0 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang/protobuf v1.5.2 - github.com/gomodule/redigo v1.8.6 + github.com/gomodule/redigo v1.8.8 github.com/google/go-cmp v0.5.6 github.com/google/go-github v17.0.0+incompatible github.com/google/go-querystring v1.1.0 // indirect @@ -39,7 +39,7 @@ require ( github.com/imdario/mergo v0.3.12 // indirect github.com/jedib0t/go-pretty v4.3.0+incompatible github.com/juliangruber/go-intersect v1.1.0 - github.com/mattn/go-sqlite3 v1.14.10 + github.com/mattn/go-sqlite3 v2.0.3+incompatible github.com/mileusna/useragent v1.0.2 github.com/minio/minio-go/v7 v7.0.20 github.com/mitchellh/copystructure v1.2.0 // indirect diff --git a/go.sum b/go.sum index 4307249b7a3..85d13001aad 100644 --- a/go.sum +++ b/go.sum @@ -345,8 +345,8 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/gomodule/redigo v1.8.6 h1:h7kHSqUl2kxeaQtVslsfUCPJ1oz2pxcyzLy4zezIzPw= -github.com/gomodule/redigo v1.8.6/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= +github.com/gomodule/redigo v1.8.8 h1:f6cXq6RRfiyrOJEV7p3JhLDlmawGBVBBP1MggY8Mo4E= +github.com/gomodule/redigo v1.8.8/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -504,8 +504,8 @@ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-sqlite3 v1.14.10 h1:MLn+5bFRlWMGoSRmJour3CL1w/qL96mvipqpwQW/Sfk= -github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= +github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI= github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= @@ -664,8 +664,6 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/studio-b12/gowebdav v0.0.0-20210917133250-a3a86976a1df h1:C+J/LwTqP8gRPt1MdSzBNZP0OYuDm5wsmDKgwpLjYzo= -github.com/studio-b12/gowebdav v0.0.0-20210917133250-a3a86976a1df/go.mod h1:gCcfDlA1Y7GqOaeEKw5l9dOGx1VLdc/HuQSlQAaZ30s= github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f h1:L2NE7BXnSlSLoNYZ0lCwZDjdnYjCNYC71k9ClZUTFTs= github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= diff --git a/internal/grpc/interceptors/auth/scope.go b/internal/grpc/interceptors/auth/scope.go index be4da102fc8..8eec835ea19 100644 --- a/internal/grpc/interceptors/auth/scope.go +++ b/internal/grpc/interceptors/auth/scope.go @@ -120,6 +120,9 @@ func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[s if utils.ResourceIDEqual(share.Share.ResourceId, ref.GetResourceId()) { return nil } + if ok, err := checkIfNestedResource(ctx, ref, share.Share.ResourceId, client, mgr); err == nil && ok { + return nil + } } } else if strings.HasPrefix(k, "publicshare") { var share link.PublicShare diff --git a/internal/grpc/services/gateway/authprovider.go b/internal/grpc/services/gateway/authprovider.go index 35960709704..2f066797ddc 100644 --- a/internal/grpc/services/gateway/authprovider.go +++ b/internal/grpc/services/gateway/authprovider.go @@ -118,6 +118,8 @@ func (s *svc) Authenticate(ctx context.Context, req *gateway.AuthenticateRequest ctx = ctxpkg.ContextSetToken(ctx, token) ctx = ctxpkg.ContextSetUser(ctx, res.User) ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, token) + + // TODO(ishank011): Add a cache for these scope, err := s.expandScopes(ctx, res.TokenScope) if err != nil { err = errors.Wrap(err, "authsvc: error expanding token scope") diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index 02a412fc848..397faa64c9f 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -35,6 +35,7 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1" typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "google.golang.org/grpc/codes" "github.com/cs3org/reva/pkg/appctx" ctxpkg "github.com/cs3org/reva/pkg/ctx" @@ -46,6 +47,8 @@ import ( "github.com/cs3org/reva/pkg/utils" "github.com/golang-jwt/jwt" "github.com/pkg/errors" + + gstatus "google.golang.org/grpc/status" ) /* About caching @@ -558,15 +561,16 @@ func (s *svc) TouchFile(ctx context.Context, req *provider.TouchFileRequest) (*p c, _, err := s.find(ctx, req.Ref) if err != nil { return &provider.TouchFileResponse{ - Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find reference %+v", req.Ref), err), + Status: status.NewStatusFromErrType(ctx, "TouchFile ref="+req.Ref.String(), err), }, nil } res, err := c.TouchFile(ctx, req) if err != nil { - return &provider.TouchFileResponse{ - Status: status.NewStatusFromErrType(ctx, "gateway could not call TouchFile", err), - }, nil + if gstatus.Code(err) == codes.PermissionDenied { + return &provider.TouchFileResponse{Status: &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}}, nil + } + return nil, errors.Wrap(err, "gateway: error calling TouchFile") } return res, nil @@ -651,9 +655,10 @@ func (s *svc) SetArbitraryMetadata(ctx context.Context, req *provider.SetArbitra res, err := c.SetArbitraryMetadata(ctx, req) if err != nil { - return &provider.SetArbitraryMetadataResponse{ - Status: status.NewStatusFromErrType(ctx, "gateway could not call SetArbitraryMetadata", err), - }, nil + if gstatus.Code(err) == codes.PermissionDenied { + return &provider.SetArbitraryMetadataResponse{Status: &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}}, nil + } + return nil, errors.Wrap(err, "gateway: error calling SetArbitraryMetadata") } s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), req.Ref.ResourceId) @@ -673,12 +678,13 @@ func (s *svc) UnsetArbitraryMetadata(ctx context.Context, req *provider.UnsetArb res, err := c.UnsetArbitraryMetadata(ctx, req) if err != nil { - return &provider.UnsetArbitraryMetadataResponse{ - Status: status.NewStatusFromErrType(ctx, "gateway could not call UnsetArbitraryMetadata", err), - }, nil + if gstatus.Code(err) == codes.PermissionDenied { + return &provider.UnsetArbitraryMetadataResponse{Status: &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}}, nil + } + return nil, errors.Wrap(err, "gateway: error calling UnsetArbitraryMetadata") } - s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), req.Ref.ResourceId) + return res, nil } @@ -695,9 +701,10 @@ func (s *svc) SetLock(ctx context.Context, req *provider.SetLockRequest) (*provi res, err := c.SetLock(ctx, req) if err != nil { - return &provider.SetLockResponse{ - Status: status.NewStatusFromErrType(ctx, "gateway could not call SetLock", err), - }, nil + if gstatus.Code(err) == codes.PermissionDenied { + return &provider.SetLockResponse{Status: &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}}, nil + } + return nil, errors.Wrap(err, "gateway: error calling SetLock") } return res, nil @@ -705,20 +712,19 @@ func (s *svc) SetLock(ctx context.Context, req *provider.SetLockRequest) (*provi // GetLock returns an existing lock on the given reference func (s *svc) GetLock(ctx context.Context, req *provider.GetLockRequest) (*provider.GetLockResponse, error) { - var c provider.ProviderAPIClient - var err error - c, _, req.Ref, err = s.findAndUnwrap(ctx, req.Ref) + c, _, err := s.find(ctx, req.Ref) if err != nil { return &provider.GetLockResponse{ - Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref), err), + Status: status.NewStatusFromErrType(ctx, "GetLock ref="+req.Ref.String(), err), }, nil } res, err := c.GetLock(ctx, req) if err != nil { - return &provider.GetLockResponse{ - Status: status.NewStatusFromErrType(ctx, "gateway could not call GetLock", err), - }, nil + if gstatus.Code(err) == codes.PermissionDenied { + return &provider.GetLockResponse{Status: &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}}, nil + } + return nil, errors.Wrap(err, "gateway: error calling GetLock") } return res, nil @@ -726,20 +732,19 @@ func (s *svc) GetLock(ctx context.Context, req *provider.GetLockRequest) (*provi // RefreshLock refreshes an existing lock on the given reference func (s *svc) RefreshLock(ctx context.Context, req *provider.RefreshLockRequest) (*provider.RefreshLockResponse, error) { - var c provider.ProviderAPIClient - var err error - c, _, req.Ref, err = s.findAndUnwrap(ctx, req.Ref) + c, _, err := s.find(ctx, req.Ref) if err != nil { return &provider.RefreshLockResponse{ - Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref), err), + Status: status.NewStatusFromErrType(ctx, "RefreshLock ref="+req.Ref.String(), err), }, nil } res, err := c.RefreshLock(ctx, req) if err != nil { - return &provider.RefreshLockResponse{ - Status: status.NewStatusFromErrType(ctx, "gateway could not call RefreshLock", err), - }, nil + if gstatus.Code(err) == codes.PermissionDenied { + return &provider.RefreshLockResponse{Status: &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}}, nil + } + return nil, errors.Wrap(err, "gateway: error calling RefreshLock") } return res, nil @@ -747,20 +752,19 @@ func (s *svc) RefreshLock(ctx context.Context, req *provider.RefreshLockRequest) // Unlock removes an existing lock from the given reference func (s *svc) Unlock(ctx context.Context, req *provider.UnlockRequest) (*provider.UnlockResponse, error) { - var c provider.ProviderAPIClient - var err error - c, _, req.Ref, err = s.findAndUnwrap(ctx, req.Ref) + c, _, err := s.find(ctx, req.Ref) if err != nil { return &provider.UnlockResponse{ - Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref), err), + Status: status.NewStatusFromErrType(ctx, "Unlock ref="+req.Ref.String(), err), }, nil } res, err := c.Unlock(ctx, req) if err != nil { - return &provider.UnlockResponse{ - Status: status.NewStatusFromErrType(ctx, "gateway could not call Unlock", err), - }, nil + if gstatus.Code(err) == codes.PermissionDenied { + return &provider.UnlockResponse{Status: &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED}}, nil + } + return nil, errors.Wrap(err, "gateway: error calling Unlock") } return res, nil diff --git a/internal/grpc/services/gateway/usershareprovider.go b/internal/grpc/services/gateway/usershareprovider.go index 8fac9cf8dbb..c3e88d16d1a 100644 --- a/internal/grpc/services/gateway/usershareprovider.go +++ b/internal/grpc/services/gateway/usershareprovider.go @@ -22,18 +22,17 @@ import ( "context" "path" - ctxpkg "github.com/cs3org/reva/pkg/ctx" - rtrace "github.com/cs3org/reva/pkg/trace" - rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/cs3org/reva/pkg/appctx" + ctxpkg "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/pkg/storage/utils/grants" + rtrace "github.com/cs3org/reva/pkg/trace" "github.com/pkg/errors" ) @@ -327,8 +326,56 @@ func (s *svc) UpdateReceivedShare(ctx context.Context, req *collaboration.Update s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), req.Share.Share.ResourceId) return c.UpdateReceivedShare(ctx, req) -} + /* + TODO: Leftover from master merge. Do we need this? + if err != nil { + appctx.GetLogger(ctx). + Err(err). + Msg("UpdateReceivedShare: failed to get user share provider") + return &collaboration.UpdateReceivedShareResponse{ + Status: status.NewInternal(ctx, "error getting share provider client"), + }, nil + } + // check if we have a resource id in the update response that we can use to update references + if res.GetShare().GetShare().GetResourceId() == nil { + log.Err(err).Msg("gateway: UpdateReceivedShare must return a ResourceId") + return &collaboration.UpdateReceivedShareResponse{ + Status: &rpc.Status{ + Code: rpc.Code_CODE_INTERNAL, + }, + }, nil + } + // properties are updated in the order they appear in the field mask + // when an error occurs the request ends and no further fields are updated + for i := range req.UpdateMask.Paths { + switch req.UpdateMask.Paths[i] { + case "state": + switch req.GetShare().GetState() { + case collaboration.ShareState_SHARE_STATE_ACCEPTED: + rpcStatus := s.createReference(ctx, res.GetShare().GetShare().GetResourceId()) + if rpcStatus.Code != rpc.Code_CODE_OK { + return &collaboration.UpdateReceivedShareResponse{Status: rpcStatus}, nil + } + case collaboration.ShareState_SHARE_STATE_REJECTED: + rpcStatus := s.removeReference(ctx, res.GetShare().GetShare().ResourceId) + if rpcStatus.Code != rpc.Code_CODE_OK && rpcStatus.Code != rpc.Code_CODE_NOT_FOUND { + return &collaboration.UpdateReceivedShareResponse{Status: rpcStatus}, nil + } + } + case "mount_point": + // TODO(labkode): implementing updating mount point + err = errtypes.NotSupported("gateway: update of mount point is not yet implemented") + return &collaboration.UpdateReceivedShareResponse{ + Status: status.NewUnimplemented(ctx, err, "error updating received share"), + }, nil + default: + return nil, errtypes.NotSupported("updating " + req.UpdateMask.Paths[i] + " is not supported") + } + } + return res, nil + */ +} func (s *svc) removeReference(ctx context.Context, resourceID *provider.ResourceId) *rpc.Status { log := appctx.GetLogger(ctx) diff --git a/pkg/app/provider/wopi/wopi.go b/pkg/app/provider/wopi/wopi.go index 5f2a38add15..cca32227132 100644 --- a/pkg/app/provider/wopi/wopi.go +++ b/pkg/app/provider/wopi/wopi.go @@ -36,6 +36,7 @@ import ( "github.com/beevik/etree" appprovider "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1" appregistry "github.com/cs3org/go-cs3apis/cs3/app/registry/v1beta1" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/pkg/app" "github.com/cs3org/reva/pkg/app/provider/registry" @@ -139,6 +140,11 @@ func (p *wopiProvider) GetAppURL(ctx context.Context, resource *provider.Resourc u, ok := ctxpkg.ContextGetUser(ctx) if ok { // else defaults to "Guest xyz" + if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT { + q.Add("userid", resource.Owner.OpaqueId+"@"+resource.Owner.Idp) + } else { + q.Add("userid", u.Id.OpaqueId+"@"+u.Id.Idp) + } var isPublicShare bool if u.Opaque != nil { if _, ok := u.Opaque.Map["public-share-role"]; ok { diff --git a/pkg/auth/manager/oidc/oidc.go b/pkg/auth/manager/oidc/oidc.go index 2ed67a26986..e882b399ba5 100644 --- a/pkg/auth/manager/oidc/oidc.go +++ b/pkg/auth/manager/oidc/oidc.go @@ -23,6 +23,7 @@ package oidc import ( "context" "fmt" + "strings" "time" oidc "github.com/coreos/go-oidc" @@ -130,6 +131,12 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) if claims["email_verified"] == nil { // This is not set in simplesamlphp claims["email_verified"] = false } + if claims["preferred_username"] == nil { + claims["preferred_username"] = claims[am.c.IDClaim] + } + if claims["name"] == nil { + claims["name"] = claims[am.c.IDClaim] + } if claims["email"] == nil { return nil, nil, fmt.Errorf("no \"email\" attribute found in userinfo: maybe the client did not request the oidc \"email\"-scope") @@ -158,7 +165,7 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) userID := &user.UserId{ OpaqueId: claims[am.c.IDClaim].(string), // a stable non reassignable id Idp: claims["issuer"].(string), // in the scope of this issuer - Type: user.UserType_USER_TYPE_PRIMARY, + Type: getUserType(claims[am.c.IDClaim].(string)), } gwc, err := pool.GetGatewayServiceClient(am.c.GatewaySvc) if err != nil { @@ -236,3 +243,17 @@ func (am *mgr) getOIDCProvider(ctx context.Context) (*oidc.Provider, error) { am.provider = provider return am.provider, nil } + +func getUserType(upn string) user.UserType { + var t user.UserType + switch { + case strings.HasPrefix(upn, "guest"): + t = user.UserType_USER_TYPE_LIGHTWEIGHT + case strings.Contains(upn, "@"): + t = user.UserType_USER_TYPE_FEDERATED + default: + t = user.UserType_USER_TYPE_PRIMARY + } + return t + +} diff --git a/pkg/auth/scope/lightweight.go b/pkg/auth/scope/lightweight.go index 8256f3f09be..5ebc8d22252 100644 --- a/pkg/auth/scope/lightweight.go +++ b/pkg/auth/scope/lightweight.go @@ -20,6 +20,7 @@ package scope import ( "context" + "strings" authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" @@ -34,12 +35,41 @@ func lightweightAccountScope(_ context.Context, scope *authpb.Scope, resource in // These cannot be resolved from here, but need to be added to the scope from // where the call to mint tokens is made. // From here, we only allow ListReceivedShares calls - if _, ok := resource.(*collaboration.ListReceivedSharesRequest); ok { + switch v := resource.(type) { + case *collaboration.ListReceivedSharesRequest: return true, nil + case string: + return checkLightweightPath(v), nil } return false, nil } +func checkLightweightPath(path string) bool { + paths := []string{ + "/ocs/v2.php/apps/files_sharing/api/v1/shares", + "/ocs/v1.php/apps/files_sharing/api/v1/shares", + "/ocs/v2.php/apps/files_sharing//api/v1/shares", + "/ocs/v1.php/apps/files_sharing//api/v1/shares", + "/ocs/v2.php/cloud/capabilities", + "/ocs/v1.php/cloud/capabilities", + "/ocs/v2.php/cloud/user", + "/ocs/v1.php/cloud/user", + "/remote.php/webdav", + "/remote.php/dav/files", + "/app/open", + "/app/new", + "/archiver", + "/dataprovider", + "/data", + } + for _, p := range paths { + if strings.HasPrefix(path, p) { + return true + } + } + return false +} + // AddLightweightAccountScope adds the scope to allow access to lightweight user. func AddLightweightAccountScope(role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { ref := &provider.Reference{Path: "/"} diff --git a/pkg/auth/scope/receivedshare.go b/pkg/auth/scope/receivedshare.go index 2974b0ca2a7..81236a3c358 100644 --- a/pkg/auth/scope/receivedshare.go +++ b/pkg/auth/scope/receivedshare.go @@ -54,7 +54,10 @@ func receivedShareScope(_ context.Context, scope *authpb.Scope, resource interfa // AddReceivedShareScope adds the scope to allow access to a received user/group share and // the shared resource. func AddReceivedShareScope(share *collaboration.ReceivedShare, role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { - val, err := utils.MarshalProtoV1ToJSON(share) + // Create a new "scope share" to only expose the required fields to the scope. + scopeShare := &collaboration.Share{Id: share.Share.Id, Owner: share.Share.Owner, Creator: share.Share.Creator, ResourceId: share.Share.ResourceId} + + val, err := utils.MarshalProtoV1ToJSON(&collaboration.ReceivedShare{Share: scopeShare}) if err != nil { return nil, err } diff --git a/pkg/auth/scope/share.go b/pkg/auth/scope/share.go index 48683dea17e..6284efd0076 100644 --- a/pkg/auth/scope/share.go +++ b/pkg/auth/scope/share.go @@ -121,7 +121,10 @@ func checkSharePath(path string) bool { // AddShareScope adds the scope to allow access to a user/group share and // the shared resource. func AddShareScope(share *collaboration.Share, role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) { - val, err := utils.MarshalProtoV1ToJSON(share) + // Create a new "scope share" to only expose the required fields to the scope. + scopeShare := &collaboration.Share{Id: share.Id, Owner: share.Owner, Creator: share.Creator, ResourceId: share.ResourceId} + + val, err := utils.MarshalProtoV1ToJSON(scopeShare) if err != nil { return nil, err } diff --git a/pkg/cbox/user/rest/rest.go b/pkg/cbox/user/rest/rest.go index eeedaba59a7..fbd1b125b2e 100644 --- a/pkg/cbox/user/rest/rest.go +++ b/pkg/cbox/user/rest/rest.go @@ -128,9 +128,7 @@ func (m *manager) Configure(ml map[string]interface{}) error { return nil } -func (m *manager) getUserByParam(ctx context.Context, param, val string) (map[string]interface{}, error) { - url := fmt.Sprintf("%s/Identity?filter=%s:%s&field=upn&field=primaryAccountEmail&field=displayName&field=uid&field=gid&field=type", - m.conf.APIBaseURL, param, url.QueryEscape(val)) +func (m *manager) getUser(ctx context.Context, url string) (map[string]interface{}, error) { responseData, err := m.apiTokenManager.SendAPIGetRequest(ctx, url, false) if err != nil { return nil, err @@ -151,17 +149,38 @@ func (m *manager) getUserByParam(ctx context.Context, param, val string) (map[st } if len(users) != 1 { - return nil, errors.New("rest: user not found: " + param + ": " + val) + return nil, errors.New("rest: user not found for URL: " + url) } return users[0], nil } +func (m *manager) getUserByParam(ctx context.Context, param, val string) (map[string]interface{}, error) { + url := fmt.Sprintf("%s/Identity?filter=%s:%s&field=upn&field=primaryAccountEmail&field=displayName&field=uid&field=gid&field=type", + m.conf.APIBaseURL, param, url.QueryEscape(val)) + return m.getUser(ctx, url) +} + +func (m *manager) getLightweightUser(ctx context.Context, mail string) (map[string]interface{}, error) { + url := fmt.Sprintf("%s/Identity?filter=primaryAccountEmail:%s&filter=upn:contains:guest&field=upn&field=primaryAccountEmail&field=displayName&field=uid&field=gid&field=type", + m.conf.APIBaseURL, url.QueryEscape(mail)) + return m.getUser(ctx, url) +} + func (m *manager) getInternalUserID(ctx context.Context, uid *userpb.UserId) (string, error) { internalID, err := m.fetchCachedInternalID(uid) if err != nil { - userData, err := m.getUserByParam(ctx, "upn", uid.OpaqueId) + var ( + userData map[string]interface{} + err error + ) + if uid.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT { + // Lightweight accounts need to be fetched by email + userData, err = m.getLightweightUser(ctx, strings.TrimPrefix(uid.OpaqueId, "guest:")) + } else { + userData, err = m.getUserByParam(ctx, "upn", uid.OpaqueId) + } if err != nil { return "", err } @@ -217,7 +236,16 @@ func (m *manager) parseAndCacheUser(ctx context.Context, userData map[string]int func (m *manager) GetUser(ctx context.Context, uid *userpb.UserId) (*userpb.User, error) { u, err := m.fetchCachedUserDetails(uid) if err != nil { - userData, err := m.getUserByParam(ctx, "upn", uid.OpaqueId) + var ( + userData map[string]interface{} + err error + ) + if uid.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT { + // Lightweight accounts need to be fetched by email + userData, err = m.getLightweightUser(ctx, strings.TrimPrefix(uid.OpaqueId, "guest:")) + } else { + userData, err = m.getUserByParam(ctx, "upn", uid.OpaqueId) + } if err != nil { return nil, err } @@ -252,7 +280,12 @@ func (m *manager) GetUserByClaim(ctx context.Context, claim, value string) (*use userData, err := m.getUserByParam(ctx, claim, value) if err != nil { - return nil, err + // Lightweight accounts need to be fetched by email + if strings.HasPrefix(value, "guest:") { + if userData, err = m.getLightweightUser(ctx, strings.TrimPrefix(value, "guest:")); err != nil { + return nil, err + } + } } u := m.parseAndCacheUser(ctx, userData) diff --git a/pkg/eosclient/eosbinary/eosbinary.go b/pkg/eosclient/eosbinary/eosbinary.go index 91b45dd0371..0da98de2d67 100644 --- a/pkg/eosclient/eosbinary/eosbinary.go +++ b/pkg/eosclient/eosbinary/eosbinary.go @@ -1001,7 +1001,7 @@ func (c *Client) parseFileInfo(raw string) (*eosclient.FileInfo, error) { }) var previousXAttr = "" for _, p := range partsBySpace { - partsByEqual := strings.Split(p, "=") // we have kv pairs like [size 14] + partsByEqual := strings.SplitN(p, "=", 2) // we have kv pairs like [size 14] if len(partsByEqual) == 2 { // handle xattrn and xattrv special cases switch { diff --git a/pkg/storage/utils/acl/acl.go b/pkg/storage/utils/acl/acl.go index 1c493c7224d..311ee1a4545 100644 --- a/pkg/storage/utils/acl/acl.go +++ b/pkg/storage/utils/acl/acl.go @@ -57,7 +57,13 @@ func Parse(acls string, delimiter string) (*ACLs, error) { if t == "" || isComment(t) { continue } - entry, err := ParseEntry(t) + var err error + var entry *Entry + if strings.HasPrefix(t, TypeLightweight) { + entry, err = ParseLWEntry(t) + } else { + entry, err = ParseEntry(t) + } if err != nil { return nil, err } @@ -133,6 +139,24 @@ func ParseEntry(singleSysACL string) (*Entry, error) { }, nil } +// ParseLWEntry parses a single lightweight ACL +func ParseLWEntry(singleSysACL string) (*Entry, error) { + if !strings.HasPrefix(singleSysACL, TypeLightweight+":") { + return nil, errInvalidACL + } + singleSysACL = strings.TrimPrefix(singleSysACL, TypeLightweight+":") + + tokens := strings.Split(singleSysACL, "=") + if len(tokens) != 2 { + return nil, errInvalidACL + } + return &Entry{ + Type: TypeLightweight, + Qualifier: tokens[0], + Permissions: tokens[1], + }, nil +} + // CitrineSerialize serializes an ACL entry for citrine EOS ACLs func (a *Entry) CitrineSerialize() string { return fmt.Sprintf("%s:%s=%s", a.Type, a.Qualifier, a.Permissions) diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index 8fc6dafd1fc..33fd5d46aa4 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -811,7 +811,18 @@ func (fs *eosfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []st if err != nil { return nil, err } - auth, err := fs.getUserAuth(ctx, u, "") + + fn := "" + if u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT { + p, err := fs.resolve(ctx, ref) + if err != nil { + return nil, errors.Wrap(err, "eosfs: error resolving reference") + } + + fn = fs.wrap(ctx, p) + } + + auth, err := fs.getUserAuth(ctx, u, fn) if err != nil { return nil, err } @@ -838,7 +849,7 @@ func (fs *eosfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []st } } - fn := fs.wrap(ctx, p) + fn = fs.wrap(ctx, p) eosFileInfo, err := fs.c.GetFileInfoByPath(ctx, auth, fn) if err != nil { return nil, err From ad4ded292153fafb5e6735aa7e76481ddedf3f33 Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Wed, 12 Jan 2022 20:16:39 +0100 Subject: [PATCH 05/49] Adjust "groupfilter" to be able to search by member name (#2436) Previously the input for the LDAP Groupfilter to lookup all groups a specific user is member of was the userpb.UserId part of the User object. I.e. it assumed we could run a single LDAP query to get all groups a user is member of by specifying the userid. However most LDAP Servers store the GroupMembership by either username (e.g. in memberUID Attribute) or by the user's DN (e.g. in member/uniqueMember). The GetUserGroups method was already updated recently to do a two-staged lookup (first lookup the user's name by Id then search the Groups by username). This change just removes the userpb.UserId template processing from the GroupFilter and replaces it with a single string (the username) to get rid of the annoying `{{.}}` template values in the config. In the future we should add a config switch to also allow lookups by member DN. --- .../ldap-usergroupfilter-template.md | 15 ++++++++++++++ pkg/user/manager/ldap/ldap.go | 20 ++++--------------- .../drone/ldap-users.toml | 2 +- .../local-mesh/ldap-users.toml | 2 +- .../local/ldap-users.toml | 2 +- 5 files changed, 22 insertions(+), 19 deletions(-) create mode 100644 changelog/unreleased/ldap-usergroupfilter-template.md diff --git a/changelog/unreleased/ldap-usergroupfilter-template.md b/changelog/unreleased/ldap-usergroupfilter-template.md new file mode 100644 index 00000000000..52b2e6b2af6 --- /dev/null +++ b/changelog/unreleased/ldap-usergroupfilter-template.md @@ -0,0 +1,15 @@ +Change: Replace template in GroupFilter for UserProvider with a simple string + +Previously the "groupfilter" configuration for the UserProvider expected a +go-template value (based of of an `userpb.UserId` as it's input). And it +assumed we could run a single LDAP query to get all groups a user is member of +by specifying the userid. However most LDAP Servers store the GroupMembership +by either username (e.g. in memberUID Attribute) or by the user's DN (e.g. in +member/uniqueMember). + +This change removes the userpb.UserId template processing from the groupfilter +and replaces it with a single string (the username) to cleanup the config a +bit. Existing configs need to be update to replace the go template references +in `groupfilter` (e.g. `{{.}}` or `{{.OpaqueId}}`) with `{{query}}`. + +https://github.com/cs3org/reva/pull/2436 diff --git a/pkg/user/manager/ldap/ldap.go b/pkg/user/manager/ldap/ldap.go index 420d67bec07..282b35e6387 100644 --- a/pkg/user/manager/ldap/ldap.go +++ b/pkg/user/manager/ldap/ldap.go @@ -43,9 +43,8 @@ func init() { } type manager struct { - c *config - userfilter *template.Template - groupfilter *template.Template + c *config + userfilter *template.Template } type config struct { @@ -124,7 +123,6 @@ func (m *manager) Configure(ml map[string]interface{}) error { if c.FindFilter == "" { c.FindFilter = c.UserFilter } - c.GroupFilter = strings.ReplaceAll(c.GroupFilter, "%s", "{{.OpaqueId}}") if c.Nobody == 0 { c.Nobody = 99 @@ -136,11 +134,6 @@ func (m *manager) Configure(ml map[string]interface{}) error { err := errors.Wrap(err, fmt.Sprintf("error parsing userfilter tpl:%s", c.UserFilter)) panic(err) } - m.groupfilter, err = template.New("gf").Funcs(sprig.TxtFuncMap()).Parse(c.GroupFilter) - if err != nil { - err := errors.Wrap(err, fmt.Sprintf("error parsing groupfilter tpl:%s", c.GroupFilter)) - panic(err) - } return nil } @@ -433,11 +426,6 @@ func (m *manager) getFindFilter(query string) string { return strings.ReplaceAll(m.c.FindFilter, "{{query}}", ldap.EscapeFilter(query)) } -func (m *manager) getGroupFilter(uid interface{}) string { - b := bytes.Buffer{} - if err := m.groupfilter.Execute(&b, uid); err != nil { - err := errors.Wrap(err, fmt.Sprintf("error executing group template: userid:%+v", uid)) - panic(err) - } - return b.String() +func (m *manager) getGroupFilter(memberName string) string { + return strings.ReplaceAll(m.c.GroupFilter, "{{query}}", ldap.EscapeFilter(memberName)) } diff --git a/tests/oc-integration-tests/drone/ldap-users.toml b/tests/oc-integration-tests/drone/ldap-users.toml index 45b693961b1..8040f6f5eef 100644 --- a/tests/oc-integration-tests/drone/ldap-users.toml +++ b/tests/oc-integration-tests/drone/ldap-users.toml @@ -37,7 +37,7 @@ base_dn="dc=owncloud,dc=com" userfilter="(&(objectclass=posixAccount)(|(entryuuid={{.OpaqueId}})(cn={{.OpaqueId}})))" findfilter="(&(objectclass=posixAccount)(|(cn={{query}}*)(displayname={{query}}*)(mail={{query}}*)))" attributefilter="(&(objectclass=posixAccount)({{attr}}={{value}}))" -groupfilter="(&(objectclass=posixGroup)(cn=*)(memberuid={{.}}))" +groupfilter="(&(objectclass=posixGroup)(cn=*)(memberuid={{query}}))" bind_username="cn=admin,dc=owncloud,dc=com" bind_password="admin" idp="http://localhost:20080" diff --git a/tests/oc-integration-tests/local-mesh/ldap-users.toml b/tests/oc-integration-tests/local-mesh/ldap-users.toml index ad3fb33106c..a3f77b313e6 100644 --- a/tests/oc-integration-tests/local-mesh/ldap-users.toml +++ b/tests/oc-integration-tests/local-mesh/ldap-users.toml @@ -38,7 +38,7 @@ base_dn="dc=owncloud,dc=com" userfilter="(&(objectclass=posixAccount)(|(uid={{.OpaqueId}})(cn={{.OpaqueId}})))" findfilter="(&(objectclass=posixAccount)(|(cn={{query}}*)(displayname={{query}}*)(mail={{query}}*)))" attributefilter="(&(objectclass=posixAccount)({{attr}}={{value}}))" -groupfilter="(&(objectclass=posixGroup)(cn=*)(memberuid={{.OpaqueId}}))" +groupfilter="(&(objectclass=posixGroup)(cn=*)(memberuid={{query}}))" bind_username="cn=admin,dc=owncloud,dc=com" bind_password="admin" idp="http://localhost:40080" diff --git a/tests/oc-integration-tests/local/ldap-users.toml b/tests/oc-integration-tests/local/ldap-users.toml index 0489855b881..65e2d00f6a5 100644 --- a/tests/oc-integration-tests/local/ldap-users.toml +++ b/tests/oc-integration-tests/local/ldap-users.toml @@ -41,7 +41,7 @@ base_dn="dc=owncloud,dc=com" userfilter="(&(objectclass=posixAccount)(|(entryuuid={{.OpaqueId}})(cn={{.OpaqueId}})))" findfilter="(&(objectclass=posixAccount)(|(cn={{query}}*)(displayname={{query}}*)(mail={{query}}*)))" attributefilter="(&(objectclass=posixAccount)({{attr}}={{value}}))" -groupfilter="(&(objectclass=posixGroup)(cn=*)(memberuid={{.}}))" +groupfilter="(&(objectclass=posixGroup)(cn=*)(memberuid={{query}}))" bind_username="cn=admin,dc=owncloud,dc=com" bind_password="admin" idp="http://localhost:20080" From 1dc9062cf49e8a2660017842f3b5537eae1b9ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Thu, 13 Jan 2022 16:58:47 +0100 Subject: [PATCH 06/49] ignore handled errors when creating spaces (#2439) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- changelog/unreleased/ignore-known-erros.md | 5 +++++ .../grpc/services/storageprovider/storageprovider.go | 12 ++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 changelog/unreleased/ignore-known-erros.md diff --git a/changelog/unreleased/ignore-known-erros.md b/changelog/unreleased/ignore-known-erros.md new file mode 100644 index 00000000000..0d759f541d1 --- /dev/null +++ b/changelog/unreleased/ignore-known-erros.md @@ -0,0 +1,5 @@ +Enhancement: ignore handled errors when creating spaces + +The CreateStorageSpace no longer logs all error cases with error level logging + +https://github.com/cs3org/reva/pull/2439 \ No newline at end of file diff --git a/internal/grpc/services/storageprovider/storageprovider.go b/internal/grpc/services/storageprovider/storageprovider.go index ad7d5f11710..0de4b1b5cab 100644 --- a/internal/grpc/services/storageprovider/storageprovider.go +++ b/internal/grpc/services/storageprovider/storageprovider.go @@ -507,13 +507,13 @@ func (s *service) CreateStorageSpace(ctx context.Context, req *provider.CreateSt st = status.NewAlreadyExists(ctx, err, "already exists") default: st = status.NewInternal(ctx, "error listing spaces") + appctx.GetLogger(ctx). + Error(). + Err(err). + Interface("status", st). + Interface("request", req). + Msg("failed to create storage space") } - appctx.GetLogger(ctx). - Error(). - Err(err). - Interface("status", st). - Interface("request", req). - Msg("failed to create storage space") return &provider.CreateStorageSpaceResponse{ Status: st, }, nil From 8c1f862c3f3702dc54a7ea520a19a59a5a0eec0d Mon Sep 17 00:00:00 2001 From: kobergj Date: Thu, 13 Jan 2022 16:59:33 +0100 Subject: [PATCH 07/49] fix statcache logic (#2440) Signed-off-by: jkoberg --- changelog/unreleased/improve_statcache.md | 5 +++++ .../grpc/services/gateway/storageprovidercache.go | 11 ++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 changelog/unreleased/improve_statcache.md diff --git a/changelog/unreleased/improve_statcache.md b/changelog/unreleased/improve_statcache.md new file mode 100644 index 00000000000..fc4750b35c4 --- /dev/null +++ b/changelog/unreleased/improve_statcache.md @@ -0,0 +1,5 @@ +Bugfix: Add ArbitraryMetadataKeys to statcache key + +Otherwise stating with and without them would return the same result (because it is cached) + +https://github.com/cs3org/reva/pull/2440 diff --git a/internal/grpc/services/gateway/storageprovidercache.go b/internal/grpc/services/gateway/storageprovidercache.go index 535ae7b9d7b..3100f126317 100644 --- a/internal/grpc/services/gateway/storageprovidercache.go +++ b/internal/grpc/services/gateway/storageprovidercache.go @@ -211,19 +211,24 @@ type cachedAPIClient struct { // generates a user specific key pointing to ref - used for statcache // a key looks like: uid:1234-1233!sid:5678-5677!oid:9923-9934!path:/path/to/source // as you see it adds "uid:"/"sid:"/"oid:" prefixes to the uuids so they can be differentiated -func statKey(user *userpb.User, ref *provider.Reference) string { +func statKey(user *userpb.User, ref *provider.Reference, metaDataKeys []string) string { if ref == nil || ref.ResourceId == nil || ref.ResourceId.StorageId == "" { return "" } - return "uid" + user.Id.OpaqueId + "!sid:" + ref.ResourceId.StorageId + "!oid:" + ref.ResourceId.OpaqueId + "!path:" + ref.Path + key := "uid" + user.Id.OpaqueId + "!sid:" + ref.ResourceId.StorageId + "!oid:" + ref.ResourceId.OpaqueId + "!path:" + ref.Path + for _, k := range metaDataKeys { + key += "!mdk:" + k + } + + return key } // Stat looks in cache first before forwarding to storage provider func (c *cachedAPIClient) Stat(ctx context.Context, in *provider.StatRequest, opts ...grpc.CallOption) (*provider.StatResponse, error) { cache := c.caches[stat] - key := statKey(ctxpkg.ContextMustGetUser(ctx), in.Ref) + key := statKey(ctxpkg.ContextMustGetUser(ctx), in.Ref, in.ArbitraryMetadataKeys) if key != "" { s := &provider.StatResponse{} if err := pullFromCache(cache, key, s); err == nil { From a3588d5741790e40fc3438817d553d91e6f02bad Mon Sep 17 00:00:00 2001 From: Swikriti Tripathi <41103328+SwikritiT@users.noreply.github.com> Date: Fri, 14 Jan 2022 10:59:10 +0545 Subject: [PATCH 08/49] [tests-only]Bump the commit id for tests edge (#2442) * Bump the commit id for tests * Adding failing tests to expected to failure --- .drone.env | 2 +- tests/acceptance/expected-failures-on-OCIS-storage.md | 2 ++ tests/acceptance/expected-failures-on-S3NG-storage.md | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.drone.env b/.drone.env index 86f199a58bd..22bb1ee8a6a 100644 --- a/.drone.env +++ b/.drone.env @@ -1,3 +1,3 @@ # The test runner source for API tests -CORE_COMMITID=09d584745d6cbd6aebc557d9b78f6130e9b99e2b +CORE_COMMITID=88dc1fc01b8afd254d59cbface6ef48670848e22 CORE_BRANCH=master diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index 3c5a6492300..d9af87b1185 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -1264,6 +1264,8 @@ _ocs: api compatibility, return correct status code_ - [apiVersions/fileVersionAuthor.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L101) - [apiVersions/fileVersionAuthor.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L128) - [apiVersions/fileVersionAuthor.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L155) +- [apiVersions/fileVersionAuthor.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L180) +- [apiVersions/fileVersionAuthor.feature:220](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L220) ### [Allow public link sharing only for certain groups feature not implemented] - [apiSharePublicLink2/allowGroupToCreatePublicLinks.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/allowGroupToCreatePublicLinks.feature#L35) diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index fe977015c0c..9d97e0b2584 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -24,6 +24,8 @@ Basic file management like up and download, move, copy, properties, quota, trash - [apiVersions/fileVersionAuthor.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L101) - [apiVersions/fileVersionAuthor.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L128) - [apiVersions/fileVersionAuthor.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L155) +- [apiVersions/fileVersionAuthor.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L180) +- [apiVersions/fileVersionAuthor.feature:220](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L220) #### [Getting information about a folder overwritten by a file gives 500 error instead of 404](https://github.com/owncloud/ocis/issues/1239) - [apiWebdavProperties1/copyFile.feature:226](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L226) From 93d8ce257b6e3462d7f50708df8c3f74c024e3ef Mon Sep 17 00:00:00 2001 From: kobergj Date: Fri, 14 Jan 2022 11:48:03 +0100 Subject: [PATCH 09/49] Fix publiclinks and decomposedfs (#2445) * decomposedfs: don't check id's containing "/" Signed-off-by: jkoberg * add changelog Signed-off-by: jkoberg --- changelog/unreleased/fix-id-checking-in-decomposedfs.md | 8 ++++++++ pkg/storage/utils/decomposedfs/spaces.go | 4 ++++ 2 files changed, 12 insertions(+) create mode 100644 changelog/unreleased/fix-id-checking-in-decomposedfs.md diff --git a/changelog/unreleased/fix-id-checking-in-decomposedfs.md b/changelog/unreleased/fix-id-checking-in-decomposedfs.md new file mode 100644 index 00000000000..94e600c97a0 --- /dev/null +++ b/changelog/unreleased/fix-id-checking-in-decomposedfs.md @@ -0,0 +1,8 @@ +Bugfix: Don't handle ids containing "/" in decomposedfs + +The storageprovider previously checked all ids without checking their validity +this lead to flaky test because it shouldn't check ids that are used from the +public storage provider + +https://github.com/cs3org/reva/pull/2445 + diff --git a/pkg/storage/utils/decomposedfs/spaces.go b/pkg/storage/utils/decomposedfs/spaces.go index 4b919377938..42440dbc2fe 100644 --- a/pkg/storage/utils/decomposedfs/spaces.go +++ b/pkg/storage/utils/decomposedfs/spaces.go @@ -25,6 +25,7 @@ import ( "os" "path/filepath" "strconv" + "strings" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" v1beta11 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" @@ -195,6 +196,9 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide } case provider.ListStorageSpacesRequest_Filter_TYPE_ID: spaceID, nodeID, _ = utils.SplitStorageSpaceID(filter[i].GetId().OpaqueId) + if strings.Contains(nodeID, "/") { + return []*provider.StorageSpace{}, nil + } } } if len(spaceTypes) == 0 { From d41f7c83961113b7923d7d46d2f602a7f206473d Mon Sep 17 00:00:00 2001 From: David Christofas Date: Mon, 17 Jan 2022 17:43:34 +0100 Subject: [PATCH 10/49] Purge spaces (#2431) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * purge shares when purging storage spaces When purging a space we also want to delete all shares in that space. This is a first naive implementation for that but ideally we want to solve the with an event system eventually to decouple the services. * purge spaces in the storage driver Spaces can now be purged in a two step process. The code currently doesn't purge the blobs though. * implement review remarks * prevent normal users from listing deleted spaces * refactor share storage id filter * implement review remarks * list correct number of trashed spaces Signed-off-by: Jörn Friedrich Dreyer Co-authored-by: Jörn Friedrich Dreyer --- changelog/unreleased/purge-spaces.md | 8 ++ .../grpc/services/gateway/storageprovider.go | 78 ++++++++++++++++++- pkg/publicshare/publicshare.go | 20 +++++ pkg/rgrpc/status/status.go | 2 + pkg/share/share.go | 20 +++++ pkg/storage/utils/decomposedfs/node/node.go | 3 + pkg/storage/utils/decomposedfs/recycle.go | 4 +- pkg/storage/utils/decomposedfs/spaces.go | 49 +++++++++++- pkg/storage/utils/decomposedfs/tree/tree.go | 6 +- 9 files changed, 177 insertions(+), 13 deletions(-) create mode 100644 changelog/unreleased/purge-spaces.md diff --git a/changelog/unreleased/purge-spaces.md b/changelog/unreleased/purge-spaces.md new file mode 100644 index 00000000000..394b06aa786 --- /dev/null +++ b/changelog/unreleased/purge-spaces.md @@ -0,0 +1,8 @@ +Enhancement: Delete shares when purging spaces + +Implemented the second step of the two step spaces delete process. +The first step is marking the space as deleted, the second step is actually purging the space. +During the second step all shares, including public shares, in the space will be deleted. +When deleting a space the blobs are currently not yet deleted since the decomposedfs will receive some changes soon. + +https://github.com/cs3org/reva/pull/2431 diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index 397faa64c9f..01b0baee295 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -32,6 +32,8 @@ import ( "github.com/BurntSushi/toml" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + collaborationv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + linkv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1" typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" @@ -40,14 +42,15 @@ import ( "github.com/cs3org/reva/pkg/appctx" ctxpkg "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/publicshare" "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/pkg/rhttp/router" sdk "github.com/cs3org/reva/pkg/sdk/common" + "github.com/cs3org/reva/pkg/share" "github.com/cs3org/reva/pkg/utils" "github.com/golang-jwt/jwt" "github.com/pkg/errors" - gstatus "google.golang.org/grpc/status" ) @@ -290,7 +293,13 @@ func (s *svc) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorag } func (s *svc) DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) (*provider.DeleteStorageSpaceResponse, error) { - // TODO: needs to be fixed + opaque := req.Opaque + var purge bool + // This is just a temporary hack until the CS3 API get's updated to have a dedicated purge parameter or a dedicated PurgeStorageSpace method. + if opaque != nil { + _, purge = opaque.Map["purge"] + } + storageid, opaqeid, err := utils.SplitStorageSpaceID(req.Id.OpaqueId) if err != nil { return &provider.DeleteStorageSpaceResponse{ @@ -309,7 +318,7 @@ func (s *svc) DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorag }, nil } - res, err := c.DeleteStorageSpace(ctx, req) + dsRes, err := c.DeleteStorageSpace(ctx, req) if err != nil { return &provider.DeleteStorageSpaceResponse{ Status: status.NewStatusFromErrType(ctx, "gateway could not call DeleteStorageSpace", err), @@ -317,7 +326,68 @@ func (s *svc) DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorag } s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), &provider.ResourceId{OpaqueId: req.Id.OpaqueId}) - return res, nil + + if dsRes.Status.Code != rpc.Code_CODE_OK { + return dsRes, nil + } + + if !purge { + return dsRes, nil + } + + log := appctx.GetLogger(ctx) + log.Debug().Msg("purging storage space") + // List all shares in this storage space + lsRes, err := s.ListShares(ctx, &collaborationv1beta1.ListSharesRequest{ + Filters: []*collaborationv1beta1.Filter{share.StorageIDFilter(storageid)}, + }) + switch { + case err != nil: + return &provider.DeleteStorageSpaceResponse{ + Status: status.NewStatusFromErrType(ctx, "gateway could not delete shares of StorageSpace", err), + }, nil + case lsRes.Status.Code != rpc.Code_CODE_OK: + return &provider.DeleteStorageSpaceResponse{ + Status: status.NewInternal(ctx, "gateway could not delete shares of StorageSpace"), + }, nil + } + for _, share := range lsRes.Shares { + rsRes, err := s.RemoveShare(ctx, &collaborationv1beta1.RemoveShareRequest{ + Ref: &collaborationv1beta1.ShareReference{ + Spec: &collaborationv1beta1.ShareReference_Id{Id: share.Id}, + }, + }) + if err != nil || rsRes.Status.Code != rpc.Code_CODE_OK { + log.Error().Err(err).Interface("status", rsRes.Status).Str("share_id", share.Id.OpaqueId).Msg("failed to delete share") + } + } + + // List all public shares in this storage space + lpsRes, err := s.ListPublicShares(ctx, &linkv1beta1.ListPublicSharesRequest{ + Filters: []*linkv1beta1.ListPublicSharesRequest_Filter{publicshare.StorageIDFilter(storageid)}, + }) + switch { + case err != nil: + return &provider.DeleteStorageSpaceResponse{ + Status: status.NewStatusFromErrType(ctx, "gateway could not delete shares of StorageSpace", err), + }, nil + case lpsRes.Status.Code != rpc.Code_CODE_OK: + return &provider.DeleteStorageSpaceResponse{ + Status: status.NewInternal(ctx, "gateway could not delete shares of StorageSpace"), + }, nil + } + for _, share := range lpsRes.Share { + rsRes, err := s.RemovePublicShare(ctx, &linkv1beta1.RemovePublicShareRequest{ + Ref: &linkv1beta1.PublicShareReference{ + Spec: &linkv1beta1.PublicShareReference_Id{Id: share.Id}, + }, + }) + if err != nil || rsRes.Status.Code != rpc.Code_CODE_OK { + log.Error().Err(err).Interface("status", rsRes.Status).Str("share_id", share.Id.OpaqueId).Msg("failed to delete share") + } + } + + return dsRes, nil } func (s *svc) GetHome(ctx context.Context, _ *provider.GetHomeRequest) (*provider.GetHomeResponse, error) { diff --git a/pkg/publicshare/publicshare.go b/pkg/publicshare/publicshare.go index 7692779484f..eb8dec3cdca 100644 --- a/pkg/publicshare/publicshare.go +++ b/pkg/publicshare/publicshare.go @@ -33,6 +33,12 @@ import ( "github.com/cs3org/reva/pkg/utils" ) +const ( + // StorageIDFilterType defines a new filter type for storage id. + // TODO: Remove this once the filter type is in the CS3 API. + StorageIDFilterType link.ListPublicSharesRequest_Filter_Type = 4 +) + // Manager manipulates public shares. type Manager interface { CreatePublicShare(ctx context.Context, u *user.User, md *provider.ResourceInfo, g *link.Grant) (*link.PublicShare, error) @@ -95,11 +101,25 @@ func ResourceIDFilter(id *provider.ResourceId) *link.ListPublicSharesRequest_Fil } } +// StorageIDFilter is an abstraction for creating filter by storage id. +func StorageIDFilter(id string) *link.ListPublicSharesRequest_Filter { + return &link.ListPublicSharesRequest_Filter{ + Type: StorageIDFilterType, + Term: &link.ListPublicSharesRequest_Filter_ResourceId{ + ResourceId: &provider.ResourceId{ + StorageId: id, + }, + }, + } +} + // MatchesFilter tests if the share passes the filter. func MatchesFilter(share *link.PublicShare, filter *link.ListPublicSharesRequest_Filter) bool { switch filter.Type { case link.ListPublicSharesRequest_Filter_TYPE_RESOURCE_ID: return utils.ResourceIDEqual(share.ResourceId, filter.GetResourceId()) + case StorageIDFilterType: + return share.ResourceId.StorageId == filter.GetResourceId().GetStorageId() default: return false } diff --git a/pkg/rgrpc/status/status.go b/pkg/rgrpc/status/status.go index 7f519eea90c..e10d85c3497 100644 --- a/pkg/rgrpc/status/status.go +++ b/pkg/rgrpc/status/status.go @@ -159,6 +159,8 @@ func NewStatusFromErrType(ctx context.Context, msg string, err error) *rpc.Statu return NewUnauthenticated(ctx, err, msg+": "+err.Error()) case codes.PermissionDenied: return NewPermissionDenied(ctx, err, msg+": "+err.Error()) + case codes.Unimplemented: + return NewUnimplemented(ctx, err, msg+": "+err.Error()) } } // the actual error can be wrapped multiple times diff --git a/pkg/share/share.go b/pkg/share/share.go index d791353acbf..9ecb37f4c34 100644 --- a/pkg/share/share.go +++ b/pkg/share/share.go @@ -29,6 +29,12 @@ import ( "google.golang.org/genproto/protobuf/field_mask" ) +const ( + // StorageIDFilterType defines a new filter type for storage id. + // TODO: Remove once this filter type is in the CS3 API. + StorageIDFilterType collaboration.Filter_Type = 7 +) + //go:generate mockery -name Manager // Manager is the interface that manipulates shares. @@ -89,6 +95,18 @@ func ResourceIDFilter(id *provider.ResourceId) *collaboration.Filter { } } +// StorageIDFilter is an abstraction for creating filter by storage id. +func StorageIDFilter(id string) *collaboration.Filter { + return &collaboration.Filter{ + Type: StorageIDFilterType, + Term: &collaboration.Filter_ResourceId{ + ResourceId: &provider.ResourceId{ + StorageId: id, + }, + }, + } +} + // IsCreatedByUser checks if the user is the owner or creator of the share. func IsCreatedByUser(share *collaboration.Share, user *userv1beta1.User) bool { return utils.UserEqual(user.Id, share.Owner) || utils.UserEqual(user.Id, share.Creator) @@ -121,6 +139,8 @@ func MatchesFilter(share *collaboration.Share, filter *collaboration.Filter) boo // This filter type is used to filter out "denial shares". These are currently implemented by having the permission "0". // I.e. if the permission is 0 we don't want to show it. return int(conversions.RoleFromResourcePermissions(share.Permissions.Permissions).OCSPermissions()) != 0 + case StorageIDFilterType: + return share.ResourceId.StorageId == filter.GetResourceId().GetStorageId() default: return false } diff --git a/pkg/storage/utils/decomposedfs/node/node.go b/pkg/storage/utils/decomposedfs/node/node.go index fd61507a307..d5ea3cf9a9d 100644 --- a/pkg/storage/utils/decomposedfs/node/node.go +++ b/pkg/storage/utils/decomposedfs/node/node.go @@ -61,6 +61,9 @@ const ( QuotaUncalculated = "-1" QuotaUnknown = "-2" QuotaUnlimited = "-3" + + // TrashIDDelimiter represents the characters used to separate the nodeid and the deletion time. + TrashIDDelimiter = ".T." ) // Node represents a node in the tree and provides methods to get a Parent or Child instance diff --git a/pkg/storage/utils/decomposedfs/recycle.go b/pkg/storage/utils/decomposedfs/recycle.go index 1876856f9af..4670e8281b2 100644 --- a/pkg/storage/utils/decomposedfs/recycle.go +++ b/pkg/storage/utils/decomposedfs/recycle.go @@ -122,7 +122,7 @@ func (fs *Decomposedfs) createTrashItem(ctx context.Context, parentNode, interme log.Error().Err(err).Msg("error reading trash link, skipping") return nil, err } - parts := strings.SplitN(filepath.Base(parentNode), ".T.", 2) + parts := strings.SplitN(filepath.Base(parentNode), node.TrashIDDelimiter, 2) if len(parts) != 2 { log.Error().Str("trashnode", trashnode).Interface("parts", parts).Msg("malformed trash link, skipping") return nil, errors.New("malformed trash link") @@ -191,7 +191,7 @@ func (fs *Decomposedfs) listTrashRoot(ctx context.Context, spaceID string) ([]*p log.Error().Err(err).Str("trashRoot", trashRoot).Str("name", names[i]).Msg("error reading trash link, skipping") continue } - parts := strings.SplitN(filepath.Base(trashnode), ".T.", 2) + parts := strings.SplitN(filepath.Base(trashnode), node.TrashIDDelimiter, 2) if len(parts) != 2 { log.Error().Err(err).Str("trashRoot", trashRoot).Str("name", names[i]).Str("trashnode", trashnode).Interface("parts", parts).Msg("malformed trash link, skipping") continue diff --git a/pkg/storage/utils/decomposedfs/spaces.go b/pkg/storage/utils/decomposedfs/spaces.go index 42440dbc2fe..a494ad833c8 100644 --- a/pkg/storage/utils/decomposedfs/spaces.go +++ b/pkg/storage/utils/decomposedfs/spaces.go @@ -344,6 +344,35 @@ func (fs *Decomposedfs) UpdateStorageSpace(ctx context.Context, req *provider.Up // DeleteStorageSpace deletes a storage space func (fs *Decomposedfs) DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) error { + opaque := req.Opaque + var purge bool + if opaque != nil { + _, purge = opaque.Map["purge"] + } + + if purge { + ip := fs.lu.InternalPath(req.Id.OpaqueId) + matches, err := filepath.Glob(ip) + if err != nil { + return err + } + + // TODO: remove blobs + if err := os.RemoveAll(matches[0]); err != nil { + return err + } + + matches, err = filepath.Glob(filepath.Join(fs.o.Root, "spaces", spaceTypeAny, req.Id.OpaqueId)) + if err != nil { + return err + } + if len(matches) != 1 { + return fmt.Errorf("delete space failed: found %d matching spaces", len(matches)) + } + + return os.RemoveAll(matches[0]) + } + spaceID := req.Id.OpaqueId matches, err := filepath.Glob(filepath.Join(fs.o.Root, "spaces", spaceTypeAny, spaceID)) @@ -352,7 +381,7 @@ func (fs *Decomposedfs) DeleteStorageSpace(ctx context.Context, req *provider.De } if len(matches) != 1 { - return fmt.Errorf("update space failed: found %d matching spaces", len(matches)) + return fmt.Errorf("delete space failed: found %d matching spaces", len(matches)) } target, err := os.Readlink(matches[0]) @@ -360,12 +389,12 @@ func (fs *Decomposedfs) DeleteStorageSpace(ctx context.Context, req *provider.De appctx.GetLogger(ctx).Error().Err(err).Str("match", matches[0]).Msg("could not read link, skipping") } - node, err := node.ReadNode(ctx, fs.lu, filepath.Base(target)) + n, err := node.ReadNode(ctx, fs.lu, filepath.Base(target)) if err != nil { return err } - err = fs.tp.Delete(ctx, node) + err = fs.tp.Delete(ctx, n) if err != nil { return err } @@ -374,7 +403,16 @@ func (fs *Decomposedfs) DeleteStorageSpace(ctx context.Context, req *provider.De if err != nil { return err } - return nil + + trashPathMatches, err := filepath.Glob(n.InternalPath() + node.TrashIDDelimiter + "*") + if err != nil { + return err + } + if len(trashPathMatches) != 1 { + return fmt.Errorf("delete space failed: found %d matching trashed spaces", len(trashPathMatches)) + } + trashPath := trashPathMatches[0] + return os.Symlink(trashPath, filepath.Join(filepath.Dir(matches[0]), filepath.Base(trashPath))) } // createHiddenSpaceFolder bootstraps a storage space root with a hidden ".space" folder used to store space related @@ -459,6 +497,9 @@ func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, if err != nil || !ok { return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to Stat the space %+v", user.Username, space)) } + if strings.Contains(space.Root.OpaqueId, node.TrashIDDelimiter) { + return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to list deleted spaces %+v", user.Username, space)) + } } space.Owner = &userv1beta1.User{ // FIXME only return a UserID, not a full blown user object diff --git a/pkg/storage/utils/decomposedfs/tree/tree.go b/pkg/storage/utils/decomposedfs/tree/tree.go index 890aac74da1..4a2669c5931 100644 --- a/pkg/storage/utils/decomposedfs/tree/tree.go +++ b/pkg/storage/utils/decomposedfs/tree/tree.go @@ -420,7 +420,7 @@ func (t *Tree) Delete(ctx context.Context, n *node.Node) (err error) { // first make node appear in the space trash // parent id and name are stored as extended attributes in the node itself trashLink := filepath.Join(t.root, "trash", n.SpaceRoot.ID, n.ID) - err = os.Symlink("../../nodes/"+n.ID+".T."+deletionTime, trashLink) + err = os.Symlink("../../nodes/"+n.ID+node.TrashIDDelimiter+deletionTime, trashLink) if err != nil { // To roll back changes // TODO unset trashOriginAttr @@ -430,7 +430,7 @@ func (t *Tree) Delete(ctx context.Context, n *node.Node) (err error) { // at this point we have a symlink pointing to a non existing destination, which is fine // rename the trashed node so it is not picked up when traversing up the tree and matches the symlink - trashPath := nodePath + ".T." + deletionTime + trashPath := nodePath + node.TrashIDDelimiter + deletionTime err = os.Rename(nodePath, trashPath) if err != nil { // To roll back changes @@ -840,7 +840,7 @@ func (t *Tree) readRecycleItem(ctx context.Context, spaceid, key, path string) ( err = recycleNode.FindStorageSpaceRoot() if path == "" || path == "/" { - parts := strings.SplitN(filepath.Base(link), ".T.", 2) + parts := strings.SplitN(filepath.Base(link), node.TrashIDDelimiter, 2) if len(parts) != 2 { appctx.GetLogger(ctx).Error().Err(err).Str("trashItem", trashItem).Interface("parts", parts).Msg("malformed trash link") return From 9dd1f75620125793741d4b1c2d59baf80c862ea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Tue, 18 Jan 2022 11:41:40 +0100 Subject: [PATCH 11/49] fix create space error message (#2452) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- changelog/unreleased/fix-create-space-error-msg.md | 5 +++++ internal/grpc/services/storageprovider/storageprovider.go | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 changelog/unreleased/fix-create-space-error-msg.md diff --git a/changelog/unreleased/fix-create-space-error-msg.md b/changelog/unreleased/fix-create-space-error-msg.md new file mode 100644 index 00000000000..adfac594a7b --- /dev/null +++ b/changelog/unreleased/fix-create-space-error-msg.md @@ -0,0 +1,5 @@ +Bugfix: fix create space error message + +Create space no longer errors with list spaces error messages. + +https://github.com/cs3org/reva/pull/2452 diff --git a/internal/grpc/services/storageprovider/storageprovider.go b/internal/grpc/services/storageprovider/storageprovider.go index 0de4b1b5cab..a3474cddd60 100644 --- a/internal/grpc/services/storageprovider/storageprovider.go +++ b/internal/grpc/services/storageprovider/storageprovider.go @@ -488,7 +488,7 @@ func (s *service) CreateStorageSpace(ctx context.Context, req *provider.CreateSt var st *rpc.Status switch err.(type) { case errtypes.IsNotFound: - st = status.NewNotFound(ctx, "not found when listing spaces") + st = status.NewNotFound(ctx, "not found when creating space") case errtypes.PermissionDenied: st = status.NewPermissionDenied(ctx, err, "permission denied") case errtypes.NotSupported: @@ -506,7 +506,7 @@ func (s *service) CreateStorageSpace(ctx context.Context, req *provider.CreateSt case errtypes.AlreadyExists: st = status.NewAlreadyExists(ctx, err, "already exists") default: - st = status.NewInternal(ctx, "error listing spaces") + st = status.NewInternal(ctx, "error creating space") appctx.GetLogger(ctx). Error(). Err(err). From 9c0ebc5a6ba0174875bc7c3c916b318b8e2d7f72 Mon Sep 17 00:00:00 2001 From: Michael Barz Date: Wed, 19 Jan 2022 11:27:16 +0100 Subject: [PATCH 12/49] fix path construction in webdav propfind (#2454) --- changelog/unreleased/webdav-path-fix.md | 5 +++++ internal/http/services/owncloud/ocdav/propfind/propfind.go | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelog/unreleased/webdav-path-fix.md diff --git a/changelog/unreleased/webdav-path-fix.md b/changelog/unreleased/webdav-path-fix.md new file mode 100644 index 00000000000..34123fa7e1f --- /dev/null +++ b/changelog/unreleased/webdav-path-fix.md @@ -0,0 +1,5 @@ +Bugfix: Fix webdav paths in PROPFINDS + +The WebDAV Api was handling paths on spaces propfinds in the wrong way. This has been fixed in the WebDAV layer. + +https://github.com/cs3org/reva/pull/2454 diff --git a/internal/http/services/owncloud/ocdav/propfind/propfind.go b/internal/http/services/owncloud/ocdav/propfind/propfind.go index 7d5a42c7b31..96f6aab6bf3 100644 --- a/internal/http/services/owncloud/ocdav/propfind/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind/propfind.go @@ -458,7 +458,7 @@ func (p *Handler) getResourceInfos(ctx context.Context, w http.ResponseWriter, r // add path to resource res.Infos[i].Path = filepath.Join(info.Path, res.Infos[i].Path) if spacesPropfind { - res.Infos[i].Path = utils.MakeRelativePath(filepath.Join(info.Path, res.Infos[i].Path)) + res.Infos[i].Path = utils.MakeRelativePath(res.Infos[i].Path) } if res.Infos[i].Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { stack = append(stack, res.Infos[i]) From 3d065e97841af9abbeb416ec03ff39c550b8e2bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Thu, 20 Jan 2022 13:03:48 +0100 Subject: [PATCH 13/49] decomposedfs: do not swallow errors when creating nodes (#2457) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- changelog/unreleased/do-not-swallow-error.md | 5 +++++ pkg/storage/utils/decomposedfs/tree/tree.go | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelog/unreleased/do-not-swallow-error.md diff --git a/changelog/unreleased/do-not-swallow-error.md b/changelog/unreleased/do-not-swallow-error.md new file mode 100644 index 00000000000..f8c094847b1 --- /dev/null +++ b/changelog/unreleased/do-not-swallow-error.md @@ -0,0 +1,5 @@ +Bugfix: do not swallow error + +decomposedfs not longer swallows errors when creating a node fails. + +https://github.com/cs3org/reva/pull/2457 \ No newline at end of file diff --git a/pkg/storage/utils/decomposedfs/tree/tree.go b/pkg/storage/utils/decomposedfs/tree/tree.go index 4a2669c5931..5bb0f8f0091 100644 --- a/pkg/storage/utils/decomposedfs/tree/tree.go +++ b/pkg/storage/utils/decomposedfs/tree/tree.go @@ -259,7 +259,7 @@ func (t *Tree) CreateDir(ctx context.Context, n *node.Node) (err error) { err = t.createNode(n, owner) if err != nil { - return nil + return } // make child appear in listings From 6106698003629b3034044414e84642db5b265ed1 Mon Sep 17 00:00:00 2001 From: David Christofas Date: Thu, 20 Jan 2022 16:46:49 +0100 Subject: [PATCH 14/49] prevent purging of enabled spaces (#2459) --- changelog/unreleased/purge-spaces.md | 1 + internal/grpc/services/storageprovider/storageprovider.go | 2 ++ pkg/storage/utils/decomposedfs/spaces.go | 4 ++++ 3 files changed, 7 insertions(+) diff --git a/changelog/unreleased/purge-spaces.md b/changelog/unreleased/purge-spaces.md index 394b06aa786..693d46950c5 100644 --- a/changelog/unreleased/purge-spaces.md +++ b/changelog/unreleased/purge-spaces.md @@ -6,3 +6,4 @@ During the second step all shares, including public shares, in the space will be When deleting a space the blobs are currently not yet deleted since the decomposedfs will receive some changes soon. https://github.com/cs3org/reva/pull/2431 +https://github.com/cs3org/reva/pull/2458 diff --git a/internal/grpc/services/storageprovider/storageprovider.go b/internal/grpc/services/storageprovider/storageprovider.go index a3474cddd60..51a7739651d 100644 --- a/internal/grpc/services/storageprovider/storageprovider.go +++ b/internal/grpc/services/storageprovider/storageprovider.go @@ -593,6 +593,8 @@ func (s *service) DeleteStorageSpace(ctx context.Context, req *provider.DeleteSt st = status.NewNotFound(ctx, "not found when deleting space") case errtypes.PermissionDenied: st = status.NewPermissionDenied(ctx, err, "permission denied") + case errtypes.BadRequest: + st = status.NewInvalidArg(ctx, err.Error()) default: st = status.NewInternal(ctx, "error deleting space: "+req.Id.String()) } diff --git a/pkg/storage/utils/decomposedfs/spaces.go b/pkg/storage/utils/decomposedfs/spaces.go index a494ad833c8..3eb7b5e72cf 100644 --- a/pkg/storage/utils/decomposedfs/spaces.go +++ b/pkg/storage/utils/decomposedfs/spaces.go @@ -35,6 +35,7 @@ import ( "github.com/cs3org/reva/pkg/appctx" ctxpkg "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/node" "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/xattrs" "github.com/cs3org/reva/pkg/utils" @@ -351,6 +352,9 @@ func (fs *Decomposedfs) DeleteStorageSpace(ctx context.Context, req *provider.De } if purge { + if !strings.Contains(req.Id.OpaqueId, node.TrashIDDelimiter) { + return errtypes.NewErrtypeFromStatus(status.NewInvalidArg(ctx, "can't purge enabled space")) + } ip := fs.lu.InternalPath(req.Id.OpaqueId) matches, err := filepath.Glob(ip) if err != nil { From cff6ba5acc5854639427ea6ac9ffe8937ad2e58a Mon Sep 17 00:00:00 2001 From: kobergj Date: Thu, 20 Jan 2022 16:47:28 +0100 Subject: [PATCH 15/49] Make gateway dumb again (#2437) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * make StatHandler dumb again Signed-off-by: jkoberg * add changelog Signed-off-by: jkoberg * use findAndUnwrap instead find Signed-off-by: jkoberg * kinda fix integration tests Signed-off-by: jkoberg * remove ListContainer logic Signed-off-by: jkoberg * decomposedfs: don't check id's containing "/" Signed-off-by: jkoberg * fix linting and integration tests Signed-off-by: jkoberg * make ListRecycle dumb again Signed-off-by: jkoberg * make RestoreRecycleItem dumb again Signed-off-by: jkoberg * don't allow cross storage restore Signed-off-by: jkoberg * make Move dumb again Signed-off-by: jkoberg * make GetPath dumb again Signed-off-by: jkoberg * try changing dav report response Signed-off-by: jkoberg * add missing import Signed-off-by: jkoberg * blind mans fix for favorites Signed-off-by: jkoberg * remove commented code and nasty bug Signed-off-by: jkoberg * Update internal/http/services/owncloud/ocdav/propfind/propfind.go Co-authored-by: Jörn Friedrich Dreyer * Update internal/http/services/owncloud/ocdav/report.go Co-authored-by: Jörn Friedrich Dreyer * Update internal/http/services/owncloud/ocdav/report.go Co-authored-by: Jörn Friedrich Dreyer Co-authored-by: Jörn Friedrich Dreyer --- .../unreleased/remove-logic-from-gateway.md | 5 + .../grpc/services/gateway/storageprovider.go | 559 ++---------------- .../sharesstorageprovider.go | 6 + .../http/services/owncloud/ocdav/report.go | 11 +- tests/integration/grpc/fixtures/gateway.toml | 2 +- .../gateway_storageprovider_static_test.go | 6 +- .../grpc/gateway_storageprovider_test.go | 96 ++- .../integration/grpc/storageprovider_test.go | 9 +- 8 files changed, 133 insertions(+), 561 deletions(-) create mode 100644 changelog/unreleased/remove-logic-from-gateway.md diff --git a/changelog/unreleased/remove-logic-from-gateway.md b/changelog/unreleased/remove-logic-from-gateway.md new file mode 100644 index 00000000000..fba9ad929e5 --- /dev/null +++ b/changelog/unreleased/remove-logic-from-gateway.md @@ -0,0 +1,5 @@ +Change: Remove logic from gateway + +The gateway will now hold no logic except forwarding the requests to other services. + +https://github.com/cs3org/reva/pull/2394 diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index 01b0baee295..16d800fd8ef 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -24,8 +24,6 @@ import ( "encoding/xml" "fmt" "net/url" - "path" - "path/filepath" "strings" "time" @@ -45,7 +43,6 @@ import ( "github.com/cs3org/reva/pkg/publicshare" "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" - "github.com/cs3org/reva/pkg/rhttp/router" sdk "github.com/cs3org/reva/pkg/sdk/common" "github.com/cs3org/reva/pkg/share" "github.com/cs3org/reva/pkg/utils" @@ -573,37 +570,15 @@ func (s *svc) InitiateFileUpload(ctx context.Context, req *provider.InitiateFile } func (s *svc) GetPath(ctx context.Context, req *provider.GetPathRequest) (*provider.GetPathResponse, error) { - ref := &provider.Reference{ResourceId: req.ResourceId} - c, p, err := s.find(ctx, ref) + c, _, ref, err := s.findAndUnwrap(ctx, &provider.Reference{ResourceId: req.ResourceId}) if err != nil { return &provider.GetPathResponse{ Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find reference %+v", ref), err), }, nil } - mountPath := "" - for _, space := range decodeSpaces(p) { - mountPath = decodePath(space) - break // TODO can there be more than one space for a path? - } - - res, err := c.GetPath(ctx, req) - if err != nil { - return &provider.GetPathResponse{ - Status: status.NewStatusFromErrType(ctx, "gateway could not call GetPath", err), - }, nil - } - - if res.Status.Code != rpc.Code_CODE_OK { - return &provider.GetPathResponse{ - Status: res.Status, - }, nil - } - - return &provider.GetPathResponse{ - Status: res.Status, - Path: filepath.Join(mountPath, res.GetPath()), - }, nil + req.ResourceId = ref.ResourceId + return c.GetPath(ctx, req) } func (s *svc) CreateContainer(ctx context.Context, req *provider.CreateContainerRequest) (*provider.CreateContainerResponse, error) { @@ -669,47 +644,31 @@ func (s *svc) Delete(ctx context.Context, req *provider.DeleteRequest) (*provide } func (s *svc) Move(ctx context.Context, req *provider.MoveRequest) (*provider.MoveResponse, error) { - var c provider.ProviderAPIClient - var sourceProviderInfo, destinationProviderInfo *registry.ProviderInfo - var err error - - c, sourceProviderInfo, req.Source, err = s.findAndUnwrap(ctx, req.Source) + c, sourceProviderInfo, sref, err := s.findAndUnwrap(ctx, req.Source) if err != nil { return &provider.MoveResponse{ Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Source), err), }, nil } - // do we try to rename the root of a mountpoint? - // TODO how do we determine if the destination resides on the same storage space? - if req.Source.Path == "." { - req.Destination.ResourceId = req.Source.ResourceId - req.Destination.Path = utils.MakeRelativePath(filepath.Base(req.Destination.Path)) - } else { - _, destinationProviderInfo, req.Destination, err = s.findAndUnwrap(ctx, req.Destination) - if err != nil { - return &provider.MoveResponse{ - Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Destination), err), - }, nil - } - - // if the storage id is the same the storage provider decides if the move is allowedy or not - if sourceProviderInfo.Address != destinationProviderInfo.Address { - return &provider.MoveResponse{ - Status: status.NewUnimplemented(ctx, nil, "gateway does not support cross storage move, use copy and delete"), - }, nil - } + _, destProviderInfo, dref, err := s.findAndUnwrap(ctx, req.Destination) + if err != nil { + return &provider.MoveResponse{ + Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Source), err), + }, nil } - s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), req.Source.ResourceId) - s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), req.Destination.ResourceId) - res, err := c.Move(ctx, req) - if err != nil { + if sourceProviderInfo.Address != destProviderInfo.Address { return &provider.MoveResponse{ - Status: status.NewStatusFromErrType(ctx, "gateway could not call Move", err), + Status: status.NewUnimplemented(ctx, nil, "gateway does not support cross storage move, use copy and delete"), }, nil } - return res, nil + + req.Source = sref + req.Destination = dref + s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), req.Source.ResourceId) + s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), req.Destination.ResourceId) + return c.Move(ctx, req) } func (s *svc) SetArbitraryMetadata(ctx context.Context, req *provider.SetArbitraryMetadataRequest) (*provider.SetArbitraryMetadataResponse, error) { @@ -840,322 +799,38 @@ func (s *svc) Unlock(ctx context.Context, req *provider.UnlockRequest) (*provide return res, nil } -// Stat returns the Resoure info for a given resource by forwarding the request to all responsible providers. -// In the simplest case there is only one provider, eg. when statting a relative or id based reference -// However the registry can return multiple providers for a reference and Stat needs to take them all into account: -// The registry returns multiple providers when -// 1. embedded providers need to be taken into account, eg: there aro two providers /foo and /bar and / is being statted -// 2. multiple providers form a virtual view, eg: there are twe providers /users/[a-k] and /users/[l-z] and /users is being statted -// In contrast to ListContainer Stat can treat these cases equally by forwarding the request to all providers and aggregating the metadata: -// - The most recent mtime determines the etag -// - The size is summed up for all providers +// Stat returns the Resoure info for a given resource by forwarding the request to the responsible provider. // TODO cache info func (s *svc) Stat(ctx context.Context, req *provider.StatRequest) (*provider.StatResponse, error) { - - requestPath := req.Ref.Path - // find the providers - providerInfos, err := s.findSpaces(ctx, req.Ref) + c, _, ref, err := s.findAndUnwrap(ctx, req.Ref) if err != nil { - // we have no provider -> not found return &provider.StatResponse{ - Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref), err), + Status: status.NewNotFound(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref)), }, nil } - var info *provider.ResourceInfo - for i := range providerInfos { - // get client for storage provider - c, err := s.getStorageProviderClient(ctx, providerInfos[i]) - if err != nil { - appctx.GetLogger(ctx).Error().Err(err).Msg("gateway: could not get storage provider client, skipping") - continue - } - - for _, space := range decodeSpaces(providerInfos[i]) { - mountPath := decodePath(space) - root := space.Root - // build reference for the provider - r := &provider.Reference{ - ResourceId: req.Ref.ResourceId, - Path: req.Ref.Path, - } - // NOTE: There are problems in the following case: - // Given a req.Ref.Path = "/projects" and a mountpath = "/projects/projectA" - // Then it will request path "/projects/projectA" from the provider - // But it should only request "/" as the ResourceId already points to the correct resource - // TODO: We need to cut the path in case the resourceId is already pointing to correct resource - if r.Path != "" && strings.HasPrefix(mountPath, r.Path) { // requesting the root in that case - No Path needed - r.Path = "/" - } - providerRef := unwrap(r, mountPath, root) - - // there are three cases: - // 1. id based references -> send to provider as is. must return the path in the space. space root can be determined by the spaceid - // 2. path based references -> replace mount point with space and forward relative reference - // 3. relative reference -> forward as is - - var currentInfo *provider.ResourceInfo - statResp, err := c.Stat(ctx, &provider.StatRequest{Opaque: req.Opaque, Ref: providerRef, ArbitraryMetadataKeys: req.ArbitraryMetadataKeys}) - if err != nil { - appctx.GetLogger(ctx).Error().Err(err).Str("service", "gateway").Msg("could not stat parent mount, skipping") - continue - } - if statResp.Status.Code != rpc.Code_CODE_OK { - appctx.GetLogger(ctx).Debug().Interface("status", statResp.Status).Str("service", "gateway").Msg("stating parent mount was not ok, skipping") - continue - } - if statResp.Info == nil { - appctx.GetLogger(ctx).Error().Err(err).Str("service", "gateway").Msg("stat response for parent mount carried no info, skipping") - continue - } - - if requestPath != "" && strings.HasPrefix(mountPath, requestPath) { // when path is used and requested path is above mount point - - // mount path might be the reuqest path for file based shares - if mountPath != requestPath { - // mountpoint is deeper than the statted path - // -> make child a folder - statResp.Info.Type = provider.ResourceType_RESOURCE_TYPE_CONTAINER - statResp.Info.MimeType = "httpd/unix-directory" - // -> unset checksums for a folder - statResp.Info.Checksum = nil - if statResp.Info.Opaque != nil { - delete(statResp.Info.Opaque.Map, "md5") - delete(statResp.Info.Opaque.Map, "adler32") - } - } - - // -> update metadata for /foo/bar -> set path to './bar'? - statResp.Info.Path = strings.TrimPrefix(mountPath, requestPath) - statResp.Info.Path, _ = router.ShiftPath(statResp.Info.Path) - statResp.Info.Path = utils.MakeRelativePath(statResp.Info.Path) - // TODO invent resourceid? - if utils.IsAbsoluteReference(req.Ref) { - statResp.Info.Path = path.Join(requestPath, statResp.Info.Path) - } - } - if statResp.Info.Id.StorageId == "" { - statResp.Info.Id.StorageId = providerInfos[i].ProviderId - } - currentInfo = statResp.Info - - if info == nil { - switch { - case utils.IsAbsolutePathReference(req.Ref): - currentInfo.Path = requestPath - case utils.IsAbsoluteReference(req.Ref): - // an id based reference needs to adjust the path in the response with the provider path - currentInfo.Path = path.Join(mountPath, currentInfo.Path) - } - info = currentInfo - } else { - // aggregate metadata - if info.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { - info.Size += currentInfo.Size - } - if info.Mtime == nil || (currentInfo.Mtime != nil && utils.TSToUnixNano(currentInfo.Mtime) > utils.TSToUnixNano(info.Mtime)) { - info.Mtime = currentInfo.Mtime - info.Etag = currentInfo.Etag - // info.Checksum = resp.Info.Checksum - } - if info.Etag == "" && info.Etag != currentInfo.Etag { - info.Etag = currentInfo.Etag - } - } - } - } - - if info == nil { - return &provider.StatResponse{Status: &rpc.Status{Code: rpc.Code_CODE_NOT_FOUND}}, nil - } - return &provider.StatResponse{Status: &rpc.Status{Code: rpc.Code_CODE_OK}, Info: info}, nil + return c.Stat(ctx, &provider.StatRequest{Opaque: req.Opaque, Ref: ref, ArbitraryMetadataKeys: req.ArbitraryMetadataKeys}) } func (s *svc) ListContainerStream(_ *provider.ListContainerStreamRequest, _ gateway.GatewayAPI_ListContainerStreamServer) error { return errtypes.NotSupported("Unimplemented") } -// ListContainer lists the Resoure infos for a given resource by forwarding the request to all responsible providers. -// In the simplest case there is only one provider, eg. when listing a relative or id based reference -// However the registry can return multiple providers for a reference and ListContainer needs to take them all into account: -// The registry returns multiple providers when -// 1. embedded providers need to be taken into account, eg: there aro two providers /foo and /bar and / is being listed -// /foo and /bar need to be added to the listing of / -// 2. multiple providers form a virtual view, eg: there are twe providers /users/[a-k] and /users/[l-z] and /users is being listed -// In contrast to Stat ListContainer has to forward the request to all providers, collect the results and aggregate the metadata: -// - The most recent mtime determines the etag of the listed collection -// - The size of the root ... is summed up for all providers -// TODO cache info +// ListContainer lists the Resoure infos for a given resource by forwarding the request to the responsible provider. func (s *svc) ListContainer(ctx context.Context, req *provider.ListContainerRequest) (*provider.ListContainerResponse, error) { - - requestPath := req.Ref.Path - // find the providers - providerInfos, err := s.findSpaces(ctx, req.Ref) + c, _, ref, err := s.findAndUnwrap(ctx, req.Ref) if err != nil { // we have no provider -> not found return &provider.ListContainerResponse{ - Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref), err), + Status: status.NewNotFound(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref)), }, nil } - // list /foo, mount points at /foo/bar, /foo/bif, /foo/bar/bam - // 1. which provider needs to be listed - // 2. which providers need to be statted - // result: - // + /foo/bif -> stat /foo/bif - // + /foo/bar -> stat /foo/bar && /foo/bar/bif (and take the youngest metadata) - - // list /foo, mount points at /foo, /foo/bif, /foo/bar/bam - // 1. which provider needs to be listed -> /foo listen - // 2. which providers need to be statted - // result: - // + /foo/fil.txt -> list /foo - // + /foo/blarg.md -> list /foo - // + /foo/bif -> stat /foo/bif - // + /foo/bar -> stat /foo/bar/bam (and construct metadata for /foo/bar) - - infos := map[string]*provider.ResourceInfo{} - for i := range providerInfos { - - // get client for storage provider - c, err := s.getStorageProviderClient(ctx, providerInfos[i]) - if err != nil { - appctx.GetLogger(ctx).Error().Err(err).Str("service", "gateway").Msg("could not get storage provider client, skipping") - continue - } - - for _, space := range decodeSpaces(providerInfos[i]) { - mountPath := decodePath(space) - root := space.Root - // build reference for the provider - copy to avoid side effects - r := &provider.Reference{ - ResourceId: req.Ref.ResourceId, - Path: req.Ref.Path, - } - // NOTE: There are problems in the following case: - // Given a req.Ref.Path = "/projects" and a mountpath = "/projects/projectA" - // Then it will request path "/projects/projectA" from the provider - // But it should only request "/" as the ResourceId already points to the correct resource - // TODO: We need to cut the path in case the resourceId is already pointing to correct resource - if r.Path != "" && strings.HasPrefix(mountPath, r.Path) { // requesting the root in that case - No Path accepted - r.Path = "/" - } - providerRef := unwrap(r, mountPath, root) - - // ref Path: ., Id: a-b-c-d, provider path: /personal/a-b-c-d, provider id: a-b-c-d -> - // ref Path: ., Id: a-b-c-d, provider path: /home, provider id: a-b-c-d -> - // ref path: /foo/mop, provider path: /foo -> list(spaceid, ./mop) - // ref path: /foo, provider path: /foo - // if the requested path matches or is below a mount point we can list on that provider - // requested path provider path - // above = /foo <=> /foo/bar -> stat(spaceid, .) -> add metadata for /foo/bar - // above = /foo <=> /foo/bar/bif -> stat(spaceid, .) -> add metadata for /foo/bar - // matches = /foo/bar <=> /foo/bar -> list(spaceid, .) - // below = /foo/bar/bif <=> /foo/bar -> list(spaceid, ./bif) - switch { - case requestPath == "": // id based request - fallthrough - case strings.HasPrefix(requestPath, "."): // space request - fallthrough - case strings.HasPrefix(requestPath, mountPath): // requested path is below mount point - rsp, err := c.ListContainer(ctx, &provider.ListContainerRequest{ - Opaque: req.Opaque, - Ref: providerRef, - ArbitraryMetadataKeys: req.ArbitraryMetadataKeys, - }) - if err != nil || rsp.Status.Code != rpc.Code_CODE_OK { - appctx.GetLogger(ctx).Error().Err(err).Str("service", "gateway").Msg("could not list provider, skipping") - continue - } - - if utils.IsAbsoluteReference(req.Ref) { - var prefix string - if utils.IsAbsolutePathReference(providerRef) { - prefix = mountPath - } else { - prefix = path.Join(mountPath, providerRef.Path) - } - for j := range rsp.Infos { - - rsp.Infos[j].Path = path.Join(prefix, rsp.Infos[j].Path) - } - } - for i := range rsp.Infos { - if info, ok := infos[rsp.Infos[i].Path]; ok { - if info.Mtime != nil && rsp.Infos[i].Mtime != nil && utils.TSToUnixNano(rsp.Infos[i].Mtime) > utils.TSToUnixNano(info.Mtime) { - continue - } - } - // replace with younger info - infos[rsp.Infos[i].Path] = rsp.Infos[i] - } - case strings.HasPrefix(mountPath, requestPath): // requested path is above mount point - // requested path provider path - // /foo <=> /foo/bar -> stat(spaceid, .) -> add metadata for /foo/bar - // /foo <=> /foo/bar/bif -> stat(spaceid, .) -> add metadata for /foo/bar, overwrite type with dir - statResp, err := c.Stat(ctx, &provider.StatRequest{ - Opaque: req.Opaque, - Ref: providerRef, - ArbitraryMetadataKeys: req.ArbitraryMetadataKeys, - }) - if err != nil { - appctx.GetLogger(ctx).Error().Err(err).Str("service", "gateway").Msg("could not stat parent mount for list, skipping") - continue - } - if statResp.Status.Code != rpc.Code_CODE_OK { - appctx.GetLogger(ctx).Debug().Interface("status", statResp.Status).Str("service", "gateway").Msg("stating parent mount for list was not ok, skipping") - continue - } - if statResp.Info == nil { - appctx.GetLogger(ctx).Error().Err(err).Str("service", "gateway").Msg("stat response for list carried no info, skipping") - continue - } - - // is the mount point a direct child of the requested resurce? only works for absolute paths ... hmmm - if filepath.Dir(mountPath) != requestPath { - // mountpoint is deeper than one level - // -> make child a folder - statResp.Info.Type = provider.ResourceType_RESOURCE_TYPE_CONTAINER - statResp.Info.MimeType = "httpd/unix-directory" - // -> unset checksums for a folder - statResp.Info.Checksum = nil - if statResp.Info.Opaque != nil { - delete(statResp.Info.Opaque.Map, "md5") - delete(statResp.Info.Opaque.Map, "adler32") - } - } - - // -> update metadata for /foo/bar -> set path to './bar'? - statResp.Info.Path = strings.TrimPrefix(mountPath, requestPath) - statResp.Info.Path, _ = router.ShiftPath(statResp.Info.Path) - statResp.Info.Path = utils.MakeRelativePath(statResp.Info.Path) - // TODO invent resourceid? or unset resourceid? derive from path? - - if utils.IsAbsoluteReference(req.Ref) { - statResp.Info.Path = path.Join(requestPath, statResp.Info.Path) - } - - if info, ok := infos[statResp.Info.Path]; !ok { - // replace with younger info - infos[statResp.Info.Path] = statResp.Info - } else if info.Mtime == nil || (statResp.Info.Mtime != nil && utils.TSToUnixNano(statResp.Info.Mtime) > utils.TSToUnixNano(info.Mtime)) { - // replace with younger info - infos[statResp.Info.Path] = statResp.Info - } - default: - log := appctx.GetLogger(ctx) - log.Err(err).Str("service", "gateway").Msg("unhandled ListContainer case") - } - } - } - returnInfos := make([]*provider.ResourceInfo, 0, len(infos)) - for path := range infos { - returnInfos = append(returnInfos, infos[path]) - } - return &provider.ListContainerResponse{ - Status: &rpc.Status{Code: rpc.Code_CODE_OK}, - Infos: returnInfos, - }, nil + return c.ListContainer(ctx, &provider.ListContainerRequest{ + Opaque: req.Opaque, + Ref: ref, + ArbitraryMetadataKeys: req.ArbitraryMetadataKeys, + }) } func (s *svc) CreateSymlink(ctx context.Context, req *provider.CreateSymlinkRequest) (*provider.CreateSymlinkResponse, error) { @@ -1174,14 +849,7 @@ func (s *svc) ListFileVersions(ctx context.Context, req *provider.ListFileVersio }, nil } - res, err := c.ListFileVersions(ctx, req) - if err != nil { - return &provider.ListFileVersionsResponse{ - Status: status.NewStatusFromErrType(ctx, "gateway could not call ListFileVersions", err), - }, nil - } - - return res, nil + return c.ListFileVersions(ctx, req) } func (s *svc) RestoreFileVersion(ctx context.Context, req *provider.RestoreFileVersionRequest) (*provider.RestoreFileVersionResponse, error) { @@ -1211,180 +879,45 @@ func (s *svc) ListRecycleStream(_ *provider.ListRecycleStreamRequest, _ gateway. // TODO use the ListRecycleRequest.Ref to only list the trash of a specific storage func (s *svc) ListRecycle(ctx context.Context, req *provider.ListRecycleRequest) (*provider.ListRecycleResponse, error) { - providerInfos, err := s.findSpaces(ctx, req.Ref) + c, _, ref, err := s.findAndUnwrap(ctx, req.Ref) if err != nil { return &provider.ListRecycleResponse{ Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref), err), }, nil } - for i := range providerInfos { - - // get client for storage provider - c, err := s.getStorageProviderClient(ctx, providerInfos[i]) - if err != nil { - return &provider.ListRecycleResponse{ - Status: status.NewStatusFromErrType(ctx, "gateway could not get storage provider client", err), - }, nil - } - - for _, space := range decodeSpaces(providerInfos[i]) { - mountPath := decodePath(space) - root := space.Root - // build reference for the provider - r := &provider.Reference{ - ResourceId: req.Ref.ResourceId, - Path: req.Ref.Path, - } - // NOTE: There are problems in the following case: - // Given a req.Ref.Path = "/projects" and a mountpath = "/projects/projectA" - // Then it will request path "/projects/projectA" from the provider - // But it should only request "/" as the ResourceId already points to the correct resource - // TODO: We need to cut the path in case the resourceId is already pointing to correct resource - if r.Path != "" && strings.HasPrefix(mountPath, r.Path) { // requesting the root in that case - No Path accepted - r.Path = "/" - } - providerRef := unwrap(r, mountPath, root) - - // there are three valid cases when listing trash - // 1. id based references of a space - // 2. path based references of a space - // 3. relative reference -> forward as is - - // we can ignore spaces below the mount point - // -> only match exact references - - res, err := c.ListRecycle(ctx, &provider.ListRecycleRequest{ - Opaque: req.Opaque, - FromTs: req.FromTs, - ToTs: req.ToTs, - Ref: providerRef, - Key: req.Key, - }) - if err != nil { - return &provider.ListRecycleResponse{ - Status: status.NewStatusFromErrType(ctx, "gateway could not call ListRecycle", err), - }, nil - } - - if utils.IsAbsoluteReference(req.Ref) { - for j := range res.RecycleItems { - // wrap(res.RecycleItems[j].Ref, p) only handles ResourceInfo - res.RecycleItems[j].Ref.Path = path.Join(mountPath, res.RecycleItems[j].Ref.Path) - } - } - - return res, nil - } - - } - - return &provider.ListRecycleResponse{ - Status: status.NewNotFound(ctx, "ListRecycle no matching provider found ref="+req.Ref.String()), - }, nil + return c.ListRecycle(ctx, &provider.ListRecycleRequest{ + Opaque: req.Opaque, + FromTs: req.FromTs, + ToTs: req.ToTs, + Ref: ref, + Key: req.Key, + }) } func (s *svc) RestoreRecycleItem(ctx context.Context, req *provider.RestoreRecycleItemRequest) (*provider.RestoreRecycleItemResponse, error) { - // requestPath := req.Ref.Path - providerInfos, err := s.findSpaces(ctx, req.Ref) + c, si, ref, err := s.findAndUnwrap(ctx, req.Ref) if err != nil { return &provider.RestoreRecycleItemResponse{ Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref), err), }, nil } - var srcProvider *registry.ProviderInfo - var srcRef *provider.Reference - for i := range providerInfos { - - for _, space := range decodeSpaces(providerInfos[i]) { - mountPath := decodePath(space) - root := space.Root - // build reference for the provider - r := &provider.Reference{ - ResourceId: req.Ref.ResourceId, - Path: req.Ref.Path, - } - // NOTE: There are problems in the following case: - // Given a req.Ref.Path = "/projects" and a mountpath = "/projects/projectA" - // Then it will request path "/projects/projectA" from the provider - // But it should only request "/" as the ResourceId already points to the correct resource - // TODO: We need to cut the path in case the resourceId is already pointing to correct resource - if r.Path != "" && strings.HasPrefix(mountPath, r.Path) { // requesting the root in that case - No Path accepted - r.Path = "/" - } - srcRef = unwrap(r, mountPath, root) - srcProvider = providerInfos[i] - break - } - if srcProvider != nil { - break - } - } - - if srcProvider == nil || srcRef == nil { - return &provider.RestoreRecycleItemResponse{ - Status: status.NewNotFound(ctx, "RestoreRecycleItemResponse no matching provider found ref="+req.Ref.String()), - }, nil - } - // find destination - dstProviderInfos, err := s.findSpaces(ctx, req.RestoreRef) + _, di, rref, err := s.findAndUnwrap(ctx, req.RestoreRef) if err != nil { return &provider.RestoreRecycleItemResponse{ - Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.RestoreRef), err), - }, nil - } - var dstProvider *registry.ProviderInfo - var dstRef *provider.Reference - for i := range dstProviderInfos { - for _, space := range decodeSpaces(dstProviderInfos[i]) { - mountPath := decodePath(space) - root := space.Root - // build reference for the provider - r := &provider.Reference{ - ResourceId: req.RestoreRef.ResourceId, - Path: req.RestoreRef.Path, - } - // NOTE: There are problems in the following case: - // Given a req.Ref.Path = "/projects" and a mountpath = "/projects/projectA" - // Then it will request path "/projects/projectA" from the provider - // But it should only request "/" as the ResourceId already points to the correct resource - // TODO: We need to cut the path in case the resourceId is already pointing to correct resource - if r.Path != "" && strings.HasPrefix(mountPath, r.Path) { // requesting the root in that case - No Path accepted - r.Path = "/" - } - dstRef = unwrap(r, mountPath, root) - dstProvider = providerInfos[i] - break - } - if dstProvider != nil { - break - } - } - - if dstProvider == nil || dstRef == nil { - return &provider.RestoreRecycleItemResponse{ - Status: status.NewNotFound(ctx, "RestoreRecycleItemResponse no matching destination provider found ref="+req.RestoreRef.String()), + Status: status.NewStatusFromErrType(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref), err), }, nil } - if srcRef.ResourceId.StorageId != dstRef.ResourceId.StorageId || srcProvider.Address != dstProvider.Address { + if si.Address != di.Address { return &provider.RestoreRecycleItemResponse{ // TODO in Move() we return an unimplemented / supported ... align? Status: status.NewPermissionDenied(ctx, err, "gateway: cross-storage restores are forbidden"), }, nil } - // get client for storage provider - c, err := s.getStorageProviderClient(ctx, srcProvider) - if err != nil { - return &provider.RestoreRecycleItemResponse{ - Status: status.NewStatusFromErrType(ctx, "gateway could not get storage provider client", err), - }, nil - } - - req.Ref = srcRef - req.RestoreRef = dstRef - + req.Ref = ref + req.RestoreRef = rref res, err := c.RestoreRecycleItem(ctx, req) if err != nil { return &provider.RestoreRecycleItemResponse{ @@ -1393,10 +926,6 @@ func (s *svc) RestoreRecycleItem(ctx context.Context, req *provider.RestoreRecyc } s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), req.Ref.ResourceId) - if req.RestoreRef != nil { - s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), req.RestoreRef.ResourceId) - } - return res, nil } diff --git a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go index 3a6f7166d35..d0a17f138ff 100644 --- a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go +++ b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go @@ -278,6 +278,12 @@ func (s *service) InitiateFileUpload(ctx context.Context, req *provider.Initiate } func (s *service) GetPath(ctx context.Context, req *provider.GetPathRequest) (*provider.GetPathResponse, error) { + // TODO: Needs to find a path for a given resourceID + // It should + // - getPath of the resourceID - probably requires owner permissions -> needs machine auth + // - getPath of every received share on the same space - needs also owner permissions -> needs machine auth + // - find the shortest root path that is a prefix of the resource path + // alternatively implement this on storageprovider - it needs to know about grants to do so return nil, gstatus.Errorf(codes.Unimplemented, "method not implemented") } diff --git a/internal/http/services/owncloud/ocdav/report.go b/internal/http/services/owncloud/ocdav/report.go index 85feefb7d50..8670074d111 100644 --- a/internal/http/services/owncloud/ocdav/report.go +++ b/internal/http/services/owncloud/ocdav/report.go @@ -22,6 +22,7 @@ import ( "encoding/xml" "io" "net/http" + "path" rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" @@ -108,14 +109,8 @@ func (s *svc) doFilterFiles(w http.ResponseWriter, r *http.Request, ff *reportFi continue } - // TODO: do we need to adjust the path? - // The paths we receive have the format /user// - // We only want the `` part. Thus we remove the /user// part. - // parts := strings.SplitN(statRes.Info.Path, "/", 4) - // if len(parts) != 4 { - // log.Error().Str("path", statRes.Info.Path).Msg("path doesn't have the expected format") - // continue - // } + // TODO: implement GetPath on storage provider to fix this + statRes.Info.Path = path.Join("/users/"+currentUser.Id.OpaqueId, statRes.Info.Path) infos = append(infos, statRes.Info) } diff --git a/tests/integration/grpc/fixtures/gateway.toml b/tests/integration/grpc/fixtures/gateway.toml index 8dd59da13a1..8c08ca761d3 100644 --- a/tests/integration/grpc/fixtures/gateway.toml +++ b/tests/integration/grpc/fixtures/gateway.toml @@ -26,7 +26,7 @@ home_template = "/users/{{.Id.OpaqueId}}" "personal" = { "mount_point" = "/users", "path_template" = "/users/{{.Space.Owner.Id.OpaqueId}}" } [grpc.services.storageregistry.drivers.spaces.providers."{{storage2_address}}".spaces] -"project" = { "mount_point" = "/users/{{.CurrentUser.Id.OpaqueId}}/Projects", "path_template" = "/users/{{.Space.Owner.Id.OpaqueId}}/Projects/{{.Space.Id.OpaqueId}}" } +"project" = { "mount_point" = "/users/[^/]+/Projects", "path_template" = "/users/{{.Space.Owner.Id.OpaqueId}}/Projects/{{.Space.Name}}" } [http] address = "{{grpc_address+1}}" diff --git a/tests/integration/grpc/gateway_storageprovider_static_test.go b/tests/integration/grpc/gateway_storageprovider_static_test.go index bc0fca2b437..7cab4add866 100644 --- a/tests/integration/grpc/gateway_storageprovider_static_test.go +++ b/tests/integration/grpc/gateway_storageprovider_static_test.go @@ -44,7 +44,9 @@ import ( // other dependencies like a userprovider if needed. // It also sets up an authenticated context and a service client to the storage // provider to be used in the assertion functions. -var _ = Describe("gateway using a static registry and a shard setup", func() { +var _ = PDescribe("gateway using a static registry and a shard setup", func() { + // TODO: Static registry relies on gateway being not dumb at the moment. So these won't work anymore + // FIXME: Bring me back please! var ( dependencies = map[string]string{} revads = map[string]*Revad{} @@ -69,7 +71,7 @@ var _ = Describe("gateway using a static registry and a shard setup", func() { }, Username: "einstein", } - homeRef = &storagep.Reference{Path: "/home"} + homeRef = &storagep.Reference{Path: "."} ) BeforeEach(func() { diff --git a/tests/integration/grpc/gateway_storageprovider_test.go b/tests/integration/grpc/gateway_storageprovider_test.go index 0de1bd45afd..b6e58950c0f 100644 --- a/tests/integration/grpc/gateway_storageprovider_test.go +++ b/tests/integration/grpc/gateway_storageprovider_test.go @@ -65,7 +65,13 @@ var _ = Describe("gateway", func() { }, Username: "einstein", } - homeRef = &storagep.Reference{Path: "/users/f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c"} + homeRef = &storagep.Reference{ + ResourceId: &storagep.ResourceId{ + StorageId: user.Id.OpaqueId, + OpaqueId: user.Id.OpaqueId, + }, + Path: ".", + } infos2Etags = func(infos []*storagep.ResourceInfo) map[string]string { etags := map[string]string{} @@ -208,7 +214,9 @@ var _ = Describe("gateway", func() { }) Describe("ListContainer", func() { - It("merges the lists of both shards", func() { + // Note: The Gateway doesn't merge any lists any more. This needs to be done by the client + // TODO: Move the tests to a place where they can actually test something + PIt("merges the lists of both shards", func() { listRes, err := serviceClient.ListContainer(ctx, &storagep.ListContainerRequest{Ref: projectsRef}) Expect(err).ToNot(HaveOccurred()) Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) @@ -216,7 +224,7 @@ var _ = Describe("gateway", func() { Expect(infos2Paths(listRes.Infos)).To(ConsistOf([]string{"/projects/a - project", "/projects/z - project"})) }) - It("propagates the etags from both shards", func() { + PIt("propagates the etags from both shards", func() { rootEtag := getProjectsEtag() listRes, err := serviceClient.ListContainer(ctx, &storagep.ListContainerRequest{Ref: projectsRef}) @@ -291,7 +299,12 @@ var _ = Describe("gateway", func() { Expect(createRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) space := createRes.StorageSpace - listRes, err := serviceClient.ListContainer(ctx, &storagep.ListContainerRequest{Ref: projectsRef}) + ref := &storagep.Reference{ + ResourceId: space.Root, + Path: ".", + } + + listRes, err := serviceClient.ListContainer(ctx, &storagep.ListContainerRequest{Ref: ref}) Expect(err).ToNot(HaveOccurred()) Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) @@ -305,12 +318,18 @@ var _ = Describe("gateway", func() { It("lists individual project spaces", func() { By("trying to list a non-existent space") - listRes, err := serviceClient.ListContainer(ctx, &storagep.ListContainerRequest{Ref: &storagep.Reference{Path: "/projects/does-not-exist"}}) + listRes, err := serviceClient.ListContainer(ctx, &storagep.ListContainerRequest{Ref: &storagep.Reference{ + ResourceId: &storagep.ResourceId{ + StorageId: "does-not-exist", + OpaqueId: "neither-supposed-to-exist", + }, + Path: ".", + }}) Expect(err).ToNot(HaveOccurred()) Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_NOT_FOUND)) By("listing an existing space") - listRes, err = serviceClient.ListContainer(ctx, &storagep.ListContainerRequest{Ref: &storagep.Reference{Path: "/projects/a - project"}}) + listRes, err = serviceClient.ListContainer(ctx, &storagep.ListContainerRequest{Ref: &storagep.Reference{ResourceId: shard1Space.Root, Path: "."}}) Expect(err).ToNot(HaveOccurred()) Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) Expect(len(listRes.Infos)).To(Equal(2)) @@ -318,7 +337,7 @@ var _ = Describe("gateway", func() { for _, i := range listRes.Infos { paths = append(paths, i.Path) } - Expect(paths).To(ConsistOf([]string{"/projects/a - project/file.txt", "/projects/a - project/.space"})) + Expect(paths).To(ConsistOf([]string{"file.txt", ".space"})) }) }) @@ -326,11 +345,12 @@ var _ = Describe("gateway", func() { Context("with a basic user storage", func() { var ( - fs storage.FS - embeddedFs storage.FS - homeSpace *storagep.StorageSpace - embeddedSpace *storagep.StorageSpace - embeddedRef *storagep.Reference + fs storage.FS + embeddedFs storage.FS + homeSpace *storagep.StorageSpace + embeddedSpace *storagep.StorageSpace + embeddedSpaceID string + embeddedRef *storagep.Reference ) BeforeEach(func() { @@ -352,8 +372,10 @@ var _ = Describe("gateway", func() { "treetime_accounting": true, }) Expect(err).ToNot(HaveOccurred()) - err = fs.CreateHome(ctx) + + r, err := serviceClient.CreateHome(ctx, &storagep.CreateHomeRequest{}) Expect(err).ToNot(HaveOccurred()) + Expect(r.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) spaces, err := fs.ListStorageSpaces(ctx, []*storagep.ListStorageSpacesRequest_Filter{}, nil) Expect(err).ToNot(HaveOccurred()) @@ -374,7 +396,7 @@ var _ = Describe("gateway", func() { "treetime_accounting": true, }) Expect(err).ToNot(HaveOccurred()) - res, err := embeddedFs.CreateStorageSpace(ctx, &storagep.CreateStorageSpaceRequest{ + res, err := serviceClient.CreateStorageSpace(ctx, &storagep.CreateStorageSpaceRequest{ Type: "project", Name: "embedded project", Owner: user, @@ -382,13 +404,20 @@ var _ = Describe("gateway", func() { Expect(err).ToNot(HaveOccurred()) Expect(res.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) embeddedSpace = res.StorageSpace - embeddedRef = &storagep.Reference{Path: path.Join(homeRef.Path, "Projects", embeddedSpace.Id.OpaqueId)} + embeddedRef = &storagep.Reference{ + ResourceId: &storagep.ResourceId{ + StorageId: embeddedSpace.Id.OpaqueId, + OpaqueId: embeddedSpace.Id.OpaqueId, + }, + Path: ".", // path.Join(homeRef.Path, "Projects", embeddedSpace.Id.OpaqueId), + } err = helpers.Upload(ctx, embeddedFs, &storagep.Reference{ResourceId: &storagep.ResourceId{StorageId: embeddedSpace.Id.OpaqueId}, Path: "/embedded.txt"}, []byte("22"), ) Expect(err).ToNot(HaveOccurred()) + embeddedSpaceID = embeddedSpace.Id.OpaqueId }) Describe("ListContainer", func() { @@ -399,27 +428,27 @@ var _ = Describe("gateway", func() { Expect(len(listRes.Infos)).To(Equal(2)) var fileInfo *storagep.ResourceInfo - var embeddedInfo *storagep.ResourceInfo + // var embeddedInfo *storagep.ResourceInfo for _, i := range listRes.Infos { - if i.Path == "/users/f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/file.txt" { + if i.Path == "file.txt" { fileInfo = i - } else if i.Path == "/users/f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/Projects" { - embeddedInfo = i - } + } // else if i.Path == "Projects" { + // embeddedInfo = i + // } } Expect(fileInfo).ToNot(BeNil()) Expect(fileInfo.Owner.OpaqueId).To(Equal(user.Id.OpaqueId)) - Expect(fileInfo.Path).To(Equal("/users/f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/file.txt")) + Expect(fileInfo.Path).To(Equal("file.txt")) Expect(fileInfo.Size).To(Equal(uint64(1))) - Expect(embeddedInfo).ToNot(BeNil()) - Expect(embeddedInfo.Owner.OpaqueId).To(Equal(user.Id.OpaqueId)) - Expect(embeddedInfo.Path).To(Equal("/users/f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/Projects")) - Expect(embeddedInfo.Size).To(Equal(uint64(2))) + // Expect(embeddedInfo).ToNot(BeNil()) + // Expect(embeddedInfo.Owner.OpaqueId).To(Equal(user.Id.OpaqueId)) + // Expect(embeddedInfo.Path).To(Equal("Projects")) + // Expect(embeddedInfo.Size).To(Equal(uint64(2))) }) - It("lists the embedded project space", func() { + PIt("lists the embedded project space", func() { listRes, err := serviceClient.ListContainer(ctx, &storagep.ListContainerRequest{Ref: embeddedRef}) Expect(err).ToNot(HaveOccurred()) Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) @@ -447,9 +476,11 @@ var _ = Describe("gateway", func() { info := statRes.Info Expect(info.Type).To(Equal(storagep.ResourceType_RESOURCE_TYPE_CONTAINER)) - Expect(info.Path).To(Equal(homeRef.Path)) + Expect(info.Path).To(Equal(user.Id.OpaqueId)) Expect(info.Owner.OpaqueId).To(Equal(user.Id.OpaqueId)) - Expect(info.Size).To(Equal(uint64(3))) // home: 1, embedded: 2 + + // TODO: size aggregating is done by the client now - so no chance testing that here + // Expect(info.Size).To(Equal(uint64(3))) // home: 1, embedded: 2 }) It("stats the embedded space", func() { @@ -459,12 +490,13 @@ var _ = Describe("gateway", func() { info := statRes.Info Expect(info.Type).To(Equal(storagep.ResourceType_RESOURCE_TYPE_CONTAINER)) - Expect(info.Path).To(Equal(embeddedRef.Path)) + Expect(info.Path).To(Equal(embeddedSpaceID)) Expect(info.Owner.OpaqueId).To(Equal(user.Id.OpaqueId)) Expect(info.Size).To(Equal(uint64(2))) }) - It("propagates Sizes from within the root space", func() { + PIt("propagates Sizes from within the root space", func() { + // TODO: this cannot work atm as the propagation is not done by the gateway anymore statRes, err := serviceClient.Stat(ctx, &storagep.StatRequest{Ref: homeRef}) Expect(err).ToNot(HaveOccurred()) Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) @@ -503,7 +535,7 @@ var _ = Describe("gateway", func() { Expect(statRes.Info.Size).To(Equal(uint64(33))) }) - It("propagates Sizes from within the embedded space", func() { + PIt("propagates Sizes from within the embedded space", func() { statRes, err := serviceClient.Stat(ctx, &storagep.StatRequest{Ref: homeRef}) Expect(err).ToNot(HaveOccurred()) Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) @@ -585,7 +617,7 @@ var _ = Describe("gateway", func() { Expect(newEtag3).ToNot(Equal(newEtag2)) }) - It("propagates Etags from within the embedded space", func() { + PIt("propagates Etags from within the embedded space", func() { statRes, err := serviceClient.Stat(ctx, &storagep.StatRequest{Ref: homeRef}) Expect(err).ToNot(HaveOccurred()) Expect(statRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) diff --git a/tests/integration/grpc/storageprovider_test.go b/tests/integration/grpc/storageprovider_test.go index e074c71f2be..36dac5e65cc 100644 --- a/tests/integration/grpc/storageprovider_test.go +++ b/tests/integration/grpc/storageprovider_test.go @@ -286,9 +286,12 @@ var _ = Describe("storage providers", func() { res, err := serviceClient.GetPath(ctx, &storagep.GetPathRequest{ResourceId: statRes.Info.Id}) Expect(err).ToNot(HaveOccurred()) - Expect(res.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) - // TODO: FIXME! - if provider != "nextcloud" { + + // TODO: FIXME both cases should work for all providers + if provider != "owncloud" { + Expect(res.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + } + if provider != "nextcloud" && provider != "owncloud" { Expect(res.Path).To(Equal(subdirPath)) } }) From d752c32b8754b3beefa25bf0b637c5cb67e6a80b Mon Sep 17 00:00:00 2001 From: Willy Kloucek <34452982+wkloucek@users.noreply.github.com> Date: Fri, 21 Jan 2022 09:19:24 +0100 Subject: [PATCH 16/49] revert downgrade of mattn/go-sqlite (#2462) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 02be6ef2f59..5facff17239 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/imdario/mergo v0.3.12 // indirect github.com/jedib0t/go-pretty v4.3.0+incompatible github.com/juliangruber/go-intersect v1.1.0 - github.com/mattn/go-sqlite3 v2.0.3+incompatible + github.com/mattn/go-sqlite3 v1.14.10 github.com/mileusna/useragent v1.0.2 github.com/minio/minio-go/v7 v7.0.20 github.com/mitchellh/copystructure v1.2.0 // indirect diff --git a/go.sum b/go.sum index 85d13001aad..b061177b681 100644 --- a/go.sum +++ b/go.sum @@ -504,8 +504,8 @@ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= -github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.10 h1:MLn+5bFRlWMGoSRmJour3CL1w/qL96mvipqpwQW/Sfk= +github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI= github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= From e1c57fe37714ffe59b7d891cc4b59cd025a5f3c2 Mon Sep 17 00:00:00 2001 From: Andre Duffeck Date: Fri, 21 Jan 2022 12:28:11 +0100 Subject: [PATCH 17/49] Do not log nodes (#2463) * Do not log whole nodes It turns out that logging whole node objects is very expensive and also spams the logs quite a bit. Instead we just log the node ID now. * Add changelog --- changelog/unreleased/do-not-log-nodes.md | 6 ++++++ pkg/storage/utils/decomposedfs/node/node.go | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 changelog/unreleased/do-not-log-nodes.md diff --git a/changelog/unreleased/do-not-log-nodes.md b/changelog/unreleased/do-not-log-nodes.md new file mode 100644 index 00000000000..c3eaf0f4930 --- /dev/null +++ b/changelog/unreleased/do-not-log-nodes.md @@ -0,0 +1,6 @@ +Enhancement: do not log whole nodes + +It turns out that logging whole node objects is very expensive and also +spams the logs quite a bit. Instead we just log the node ID now. + +https://github.com/cs3org/reva/pull/2463 diff --git a/pkg/storage/utils/decomposedfs/node/node.go b/pkg/storage/utils/decomposedfs/node/node.go index d5ea3cf9a9d..273fac5ed46 100644 --- a/pkg/storage/utils/decomposedfs/node/node.go +++ b/pkg/storage/utils/decomposedfs/node/node.go @@ -482,7 +482,7 @@ func (n *Node) SetFavorite(uid *userpb.UserId, val string) error { // AsResourceInfo return the node as CS3 ResourceInfo func (n *Node) AsResourceInfo(ctx context.Context, rp *provider.ResourcePermissions, mdKeys []string, returnBasename bool) (ri *provider.ResourceInfo, err error) { - sublog := appctx.GetLogger(ctx).With().Interface("node", n).Logger() + sublog := appctx.GetLogger(ctx).With().Interface("node", n.ID).Logger() var fn string nodePath := n.lu.InternalPath(n.ID) From 59d1aa30eb60f5ac83c763c68298e6f7dda121c0 Mon Sep 17 00:00:00 2001 From: kobergj Date: Fri, 21 Jan 2022 14:48:12 +0100 Subject: [PATCH 18/49] Space grants (#2464) * send spacegrants and pass them to decomposedfs Signed-off-by: jkoberg * add changelog item Signed-off-by: jkoberg --- changelog/unreleased/extract-space-grants.md | 9 +++++++++ .../grpc/services/storageprovider/storageprovider.go | 9 +++++++++ .../ocs/handlers/apps/sharing/shares/spaces.go | 10 +++++++++- pkg/storage/utils/decomposedfs/grants.go | 6 ++---- pkg/storage/utils/decomposedfs/spaces.go | 2 +- pkg/utils/utils.go | 3 +++ 6 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 changelog/unreleased/extract-space-grants.md diff --git a/changelog/unreleased/extract-space-grants.md b/changelog/unreleased/extract-space-grants.md new file mode 100644 index 00000000000..2aa09f8114f --- /dev/null +++ b/changelog/unreleased/extract-space-grants.md @@ -0,0 +1,9 @@ +Bugfix: pass spacegrants when adding member to space + +When creating a space grant there should not be created a new space. +Unfortunately SpaceGrant didn't work when adding members to a space. +Now a value is placed in the ctx of the storageprovider on which decomposedfs reacts + + + +https://github.com/cs3org/reva/pull/2464 diff --git a/internal/grpc/services/storageprovider/storageprovider.go b/internal/grpc/services/storageprovider/storageprovider.go index 51a7739651d..e1f0505b8b3 100644 --- a/internal/grpc/services/storageprovider/storageprovider.go +++ b/internal/grpc/services/storageprovider/storageprovider.go @@ -1128,6 +1128,15 @@ func (s *service) DenyGrant(ctx context.Context, req *provider.DenyGrantRequest) } func (s *service) AddGrant(ctx context.Context, req *provider.AddGrantRequest) (*provider.AddGrantResponse, error) { + // TODO: update CS3 APIs + if req.Opaque != nil { + _, spacegrant := req.Opaque.Map["spacegrant"] + if spacegrant { + ctx = context.WithValue(ctx, utils.SpaceGrant, struct{}{}) + } + + } + // check grantee type is valid if req.Grant.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_INVALID { return &provider.AddGrantResponse{ diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/spaces.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/spaces.go index 602b215bef6..0d7945dbc17 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/spaces.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/spaces.go @@ -102,8 +102,16 @@ func (h *Handler) addSpaceMember(w http.ResponseWriter, r *http.Request, info *p return } + // TODO: change CS3 APIs + opaque := &types.Opaque{ + Map: map[string]*types.OpaqueEntry{ + "spacegrant": {}, + }, + } + addGrantRes, err := providerClient.AddGrant(ctx, &provider.AddGrantRequest{ - Ref: ref, + Opaque: opaque, + Ref: ref, Grant: &provider.Grant{ Grantee: &grantee, Permissions: role.CS3ResourcePermissions(), diff --git a/pkg/storage/utils/decomposedfs/grants.go b/pkg/storage/utils/decomposedfs/grants.go index fd94911e8c3..8d0791c3d2c 100644 --- a/pkg/storage/utils/decomposedfs/grants.go +++ b/pkg/storage/utils/decomposedfs/grants.go @@ -29,12 +29,10 @@ import ( "github.com/cs3org/reva/pkg/storage/utils/ace" "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/node" "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/xattrs" + "github.com/cs3org/reva/pkg/utils" "github.com/pkg/xattr" ) -// SpaceGrant is the key used to signal not to create a new space when a grant is assigned to a storage space. -var SpaceGrant struct{} - // DenyGrant denies access to a resource. func (fs *Decomposedfs) DenyGrant(ctx context.Context, ref *provider.Reference, g *provider.Grantee) error { return errtypes.NotSupported("decomposedfs: not supported") @@ -72,7 +70,7 @@ func (fs *Decomposedfs) AddGrant(ctx context.Context, ref *provider.Reference, g } // when a grant is added to a space, do not add a new space under "shares" - if spaceGrant := ctx.Value(SpaceGrant); spaceGrant == nil { + if spaceGrant := ctx.Value(utils.SpaceGrant); spaceGrant == nil { err := fs.createStorageSpace(ctx, "share", node.ID) if err != nil { return err diff --git a/pkg/storage/utils/decomposedfs/spaces.go b/pkg/storage/utils/decomposedfs/spaces.go index 3eb7b5e72cf..8cb869119a3 100644 --- a/pkg/storage/utils/decomposedfs/spaces.go +++ b/pkg/storage/utils/decomposedfs/spaces.go @@ -143,7 +143,7 @@ func (fs *Decomposedfs) CreateStorageSpace(ctx context.Context, req *provider.Cr }, } - ctx = context.WithValue(ctx, SpaceGrant, struct{}{}) + ctx = context.WithValue(ctx, utils.SpaceGrant, struct{}{}) if err := fs.AddGrant(ctx, &provider.Reference{ ResourceId: resp.StorageSpace.Root, diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 9134f7eaa74..3a350fa3483 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -55,6 +55,9 @@ var ( // PublicStorageProviderID is the id used by the sharestorageprovider PublicStorageProviderID = "7993447f-687f-490d-875c-ac95e89a62a4" + + // SpaceGrant is used to signal the storageprovider that the grant is on a space + SpaceGrant struct{} ) // Skip evaluates whether a source endpoint contains any of the prefixes. From 6735e44e23d7f8e7077c7003cbce5dab08d912ef Mon Sep 17 00:00:00 2001 From: Amrita <54478846+amrita-shrestha@users.noreply.github.com> Date: Mon, 24 Jan 2022 20:56:46 +0545 Subject: [PATCH 19/49] [tests-only]Bump the commit id for tests (#2453) * Bump the commit id for tests * skip personalSpace tests for now * skip issue-ocis-3023 tests to avoid infinite loop Co-authored-by: Phil Davis --- .drone.env | 2 +- .drone.star | 4 ++-- .../expected-failures-on-OCIS-storage.md | 21 ++++++++----------- .../expected-failures-on-S3NG-storage.md | 21 ++++++++----------- 4 files changed, 21 insertions(+), 27 deletions(-) diff --git a/.drone.env b/.drone.env index 22bb1ee8a6a..d0a052d1f26 100644 --- a/.drone.env +++ b/.drone.env @@ -1,3 +1,3 @@ # The test runner source for API tests -CORE_COMMITID=88dc1fc01b8afd254d59cbface6ef48670848e22 +CORE_COMMITID=a91d8d0f045c512aa76ecc92372a0b5ae7b4e3cd CORE_BRANCH=master diff --git a/.drone.star b/.drone.star index c028b1fa5e5..163250822f4 100644 --- a/.drone.star +++ b/.drone.star @@ -716,7 +716,7 @@ def ocisIntegrationTests(parallelRuns, skipExceptParts = []): "REVA_LDAP_HOSTNAME": "ldap", "TEST_REVA": "true", "SEND_SCENARIO_LINE_REFERENCES": "true", - "BEHAT_FILTER_TAGS": "~@notToImplementOnOCIS&&~@toImplementOnOCIS&&~comments-app-required&&~@federation-app-required&&~@notifications-app-required&&~systemtags-app-required&&~@provisioning_api-app-required&&~@preview-extension-required&&~@local_storage&&~@skipOnOcis-OCIS-Storage&&~@skipOnOcis", + "BEHAT_FILTER_TAGS": "~@notToImplementOnOCIS&&~@toImplementOnOCIS&&~comments-app-required&&~@federation-app-required&&~@notifications-app-required&&~systemtags-app-required&&~@provisioning_api-app-required&&~@preview-extension-required&&~@local_storage&&~@skipOnOcis-OCIS-Storage&&~@skipOnOcis&&~@personalSpace&&~@issue-ocis-3023", "DIVIDE_INTO_NUM_PARTS": parallelRuns, "RUN_PART": runPart, "EXPECTED_FAILURES_FILE": "/drone/src/tests/acceptance/expected-failures-on-OCIS-storage.md", @@ -792,7 +792,7 @@ def s3ngIntegrationTests(parallelRuns, skipExceptParts = []): "REVA_LDAP_HOSTNAME": "ldap", "TEST_REVA": "true", "SEND_SCENARIO_LINE_REFERENCES": "true", - "BEHAT_FILTER_TAGS": "~@notToImplementOnOCIS&&~@toImplementOnOCIS&&~comments-app-required&&~@federation-app-required&&~@notifications-app-required&&~systemtags-app-required&&~@provisioning_api-app-required&&~@preview-extension-required&&~@local_storage&&~@skipOnOcis-OCIS-Storage&&~@skipOnOcis", + "BEHAT_FILTER_TAGS": "~@notToImplementOnOCIS&&~@toImplementOnOCIS&&~comments-app-required&&~@federation-app-required&&~@notifications-app-required&&~systemtags-app-required&&~@provisioning_api-app-required&&~@preview-extension-required&&~@local_storage&&~@skipOnOcis-OCIS-Storage&&~@skipOnOcis&&~@personalSpace&&~@issue-ocis-3023", "DIVIDE_INTO_NUM_PARTS": parallelRuns, "RUN_PART": runPart, "EXPECTED_FAILURES_FILE": "/drone/src/tests/acceptance/expected-failures-on-S3NG-storage.md", diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index d9af87b1185..6d1c0833c48 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -34,7 +34,7 @@ _ocdav: double check the webdav property parsing when custom namespaces are used Synchronization features like etag propagation, setting mtime and locking files #### [Uploading an old method chunked file with checksum should fail using new DAV path](https://github.com/owncloud/ocis/issues/2323) -- [apiMain/checksums.feature:263](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L263) +- [apiMain/checksums.feature:369](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L369) #### [Webdav LOCK operations](https://github.com/owncloud/ocis/issues/1284) - [apiWebdavLocks/exclusiveLocks.feature:18](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L18) @@ -899,14 +899,14 @@ Scenario Outline: search for entry with emoji by pattern - [apiWebdavOperations/search.feature:255](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L255) Scenario: search for entries across various folders by tags using REPORT method And other missing implementation of favorites -- [apiFavorites/favorites.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L128) -- [apiFavorites/favorites.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L129) -- [apiFavorites/favorites.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L148) -- [apiFavorites/favorites.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L149) -- [apiFavorites/favorites.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L176) -- [apiFavorites/favorites.feature:177](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L177) -- [apiFavorites/favoritesSharingToShares.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L62) -- [apiFavorites/favoritesSharingToShares.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L63) +- [apiFavorites/favorites.feature:158](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L158) +- [apiFavorites/favorites.feature:159](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L159) +- [apiFavorites/favorites.feature:183](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L183) +- [apiFavorites/favorites.feature:184](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L184) +- [apiFavorites/favorites.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L216) +- [apiFavorites/favorites.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L217) +- [apiFavorites/favoritesSharingToShares.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L77) +- [apiFavorites/favoritesSharingToShares.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L78) #### [WWW-Authenticate header for unauthenticated requests is not clear](https://github.com/owncloud/ocis/issues/2285) Scenario Outline: Unauthenticated call @@ -1293,6 +1293,3 @@ _ocs: api compatibility, return correct status code_ ### [send PUT requests to another user's webDav endpoints as normal user](https://github.com/owncloud/ocis/issues/2893) - [apiAuthWebDav/webDavPUTAuth.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPUTAuth.feature#L40) - -### [Calculate personal space id in tests when running on reva](https://github.com/owncloud/core/issues/39617) -- [apiWebdavUpload1/uploadFile.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L32) diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index 9d97e0b2584..1221c04c360 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -48,7 +48,7 @@ _ocdav: double check the webdav property parsing when custom namespaces are used Synchronization features like etag propagation, setting mtime and locking files #### [Uploading an old method chunked file with checksum should fail using new DAV path](https://github.com/owncloud/ocis/issues/2323) -- [apiMain/checksums.feature:263](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L263) +- [apiMain/checksums.feature:369](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L369) #### [Webdav LOCK operations](https://github.com/owncloud/ocis/issues/1284) - [apiWebdavLocks/exclusiveLocks.feature:18](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L18) @@ -917,14 +917,14 @@ Scenario Outline: search for entry with emoji by pattern - [apiWebdavOperations/search.feature:255](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L255) Scenario: search for entries across various folders by tags using REPORT method And other missing implementation of favorites -- [apiFavorites/favorites.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L128) -- [apiFavorites/favorites.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L129) -- [apiFavorites/favorites.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L148) -- [apiFavorites/favorites.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L149) -- [apiFavorites/favorites.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L176) -- [apiFavorites/favorites.feature:177](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L177) -- [apiFavorites/favoritesSharingToShares.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L62) -- [apiFavorites/favoritesSharingToShares.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L63) +- [apiFavorites/favorites.feature:158](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L158) +- [apiFavorites/favorites.feature:159](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L159) +- [apiFavorites/favorites.feature:183](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L183) +- [apiFavorites/favorites.feature:184](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L184) +- [apiFavorites/favorites.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L216) +- [apiFavorites/favorites.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L217) +- [apiFavorites/favoritesSharingToShares.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L77) +- [apiFavorites/favoritesSharingToShares.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L78) #### [WWW-Authenticate header for unauthenticated requests is not clear](https://github.com/owncloud/ocis/issues/2285) Scenario Outline: Unauthenticated call @@ -1295,6 +1295,3 @@ _ocs: api compatibility, return correct status code_ ### [send PUT requests to another user's webDav endpoints as normal user](https://github.com/owncloud/ocis/issues/2893) - [apiAuthWebDav/webDavPUTAuth.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPUTAuth.feature#L40) - -### [Calculate personal space id in tests when running on reva](https://github.com/owncloud/core/issues/39617) -- [apiWebdavUpload1/uploadFile.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L32) From 0542f337831bdfec4d81b510320c09ad4324903b Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Wed, 26 Jan 2022 10:26:56 +0545 Subject: [PATCH 20/49] Bump commit id for tests 2022-01-25 (edge) (#2475) --- .drone.env | 2 +- .../expected-failures-on-OCIS-storage.md | 20 +++++++++---------- .../expected-failures-on-S3NG-storage.md | 20 +++++++++---------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.drone.env b/.drone.env index d0a052d1f26..a336b97fad7 100644 --- a/.drone.env +++ b/.drone.env @@ -1,3 +1,3 @@ # The test runner source for API tests -CORE_COMMITID=a91d8d0f045c512aa76ecc92372a0b5ae7b4e3cd +CORE_COMMITID=a8b0dd3d164da66e060290e23b278817d444d15d CORE_BRANCH=master diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index 6d1c0833c48..20302826e6e 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -569,16 +569,16 @@ Scenario Outline: Retrieving folder quota when quota is set and a file was recei #### [changing user quota gives ocs status 103 / Cannot set quota](https://github.com/owncloud/product/issues/247) _requires a [CS3 user provisioning api that can update the quota for a user](https://github.com/cs3org/cs3apis/pull/95#issuecomment-772780683)_ -- [apiShareOperationsToShares2/uploadToShare.feature:168](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L168) -- [apiShareOperationsToShares2/uploadToShare.feature:169](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L169) -- [apiShareOperationsToShares2/uploadToShare.feature:188](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L188) -- [apiShareOperationsToShares2/uploadToShare.feature:189](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L189) -- [apiShareOperationsToShares2/uploadToShare.feature:210](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L210) -- [apiShareOperationsToShares2/uploadToShare.feature:211](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L211) -- [apiShareOperationsToShares2/uploadToShare.feature:230](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L230) -- [apiShareOperationsToShares2/uploadToShare.feature:231](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L231) -- [apiShareOperationsToShares2/uploadToShare.feature:252](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L252) -- [apiShareOperationsToShares2/uploadToShare.feature:253](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L253) +- [apiShareOperationsToShares2/uploadToShare.feature:193](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L193) +- [apiShareOperationsToShares2/uploadToShare.feature:194](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L194) +- [apiShareOperationsToShares2/uploadToShare.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L218) +- [apiShareOperationsToShares2/uploadToShare.feature:219](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L219) +- [apiShareOperationsToShares2/uploadToShare.feature:245](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L245) +- [apiShareOperationsToShares2/uploadToShare.feature:246](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L246) +- [apiShareOperationsToShares2/uploadToShare.feature:270](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L270) +- [apiShareOperationsToShares2/uploadToShare.feature:271](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L271) +- [apiShareOperationsToShares2/uploadToShare.feature:297](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L297) +- [apiShareOperationsToShares2/uploadToShare.feature:298](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L298) #### [not possible to move file into a received folder](https://github.com/owncloud/ocis/issues/764) - [apiShareOperationsToShares1/changingFilesShare.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L24) diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index 1221c04c360..ccb9054c4e5 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -558,16 +558,16 @@ Scenario Outline: Retrieving folder quota when quota is set and a file was recei #### [changing user quota gives ocs status 103 / Cannot set quota](https://github.com/owncloud/product/issues/247) _requires a [CS3 user provisioning api that can update the quota for a user](https://github.com/cs3org/cs3apis/pull/95#issuecomment-772780683)_ -- [apiShareOperationsToShares2/uploadToShare.feature:168](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L168) -- [apiShareOperationsToShares2/uploadToShare.feature:169](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L169) -- [apiShareOperationsToShares2/uploadToShare.feature:188](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L188) -- [apiShareOperationsToShares2/uploadToShare.feature:189](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L189) -- [apiShareOperationsToShares2/uploadToShare.feature:210](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L210) -- [apiShareOperationsToShares2/uploadToShare.feature:211](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L211) -- [apiShareOperationsToShares2/uploadToShare.feature:230](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L230) -- [apiShareOperationsToShares2/uploadToShare.feature:231](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L231) -- [apiShareOperationsToShares2/uploadToShare.feature:252](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L252) -- [apiShareOperationsToShares2/uploadToShare.feature:253](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L253) +- [apiShareOperationsToShares2/uploadToShare.feature:193](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L193) +- [apiShareOperationsToShares2/uploadToShare.feature:194](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L194) +- [apiShareOperationsToShares2/uploadToShare.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L218) +- [apiShareOperationsToShares2/uploadToShare.feature:219](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L219) +- [apiShareOperationsToShares2/uploadToShare.feature:245](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L245) +- [apiShareOperationsToShares2/uploadToShare.feature:246](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L246) +- [apiShareOperationsToShares2/uploadToShare.feature:270](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L270) +- [apiShareOperationsToShares2/uploadToShare.feature:271](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L271) +- [apiShareOperationsToShares2/uploadToShare.feature:297](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L2597) +- [apiShareOperationsToShares2/uploadToShare.feature:298](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L298) #### [not possible to move file into a received folder](https://github.com/owncloud/ocis/issues/764) From 581e09fd1429236845dd33d6438b1a917f1d4a0e Mon Sep 17 00:00:00 2001 From: Amrita <54478846+amrita-shrestha@users.noreply.github.com> Date: Wed, 26 Jan 2022 15:54:59 +0545 Subject: [PATCH 21/49] Bump commit id for ocis issue 3030 (#2477) --- .drone.env | 2 +- .../expected-failures-on-OCIS-storage.md | 30 ++++++++++++------- .../expected-failures-on-S3NG-storage.md | 30 ++++++++++++------- 3 files changed, 41 insertions(+), 21 deletions(-) diff --git a/.drone.env b/.drone.env index a336b97fad7..d0afb183e24 100644 --- a/.drone.env +++ b/.drone.env @@ -1,3 +1,3 @@ # The test runner source for API tests -CORE_COMMITID=a8b0dd3d164da66e060290e23b278817d444d15d +CORE_COMMITID=65a12d1858c0708cb1ca2ad620e139e0d33b73ae CORE_BRANCH=master diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index 20302826e6e..9b3b9d206a0 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -841,23 +841,23 @@ API, search, favorites, config, capabilities, not existing endpoints, CORS and o #### [Trying to access another user's file gives http 403 instead of 404](https://github.com/owncloud/ocis/issues/2175) _ocdav: api compatibility, return correct status code_ -- [apiAuthWebDav/webDavMKCOLAuth.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMKCOLAuth.feature#L36) Scenario: send MKCOL requests to another user's webDav endpoints as normal user +- [apiAuthWebDav/webDavMKCOLAuth.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMKCOLAuth.feature#L54) Scenario: send MKCOL requests to another user's webDav endpoints as normal user #### [trying to lock file of another user gives http 200](https://github.com/owncloud/ocis/issues/2176) -- [apiAuthWebDav/webDavLOCKAuth.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavLOCKAuth.feature#L40) Scenario: send LOCK requests to another user's webDav endpoints as normal user +- [apiAuthWebDav/webDavLOCKAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavLOCKAuth.feature#L58) Scenario: send LOCK requests to another user's webDav endpoints as normal user #### [Renaming a resource to banned name is allowed](https://github.com/owncloud/ocis/issues/1295) _ocdav: api compatibility, return correct status code_ -- [apiAuthWebDav/webDavMOVEAuth.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMOVEAuth.feature#L39) Scenario: send MOVE requests to another user's webDav endpoints as normal user +- [apiAuthWebDav/webDavMOVEAuth.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMOVEAuth.feature#L57) Scenario: send MOVE requests to another user's webDav endpoints as normal user #### [send POST requests to another user's webDav endpoints as normal user](https://github.com/owncloud/ocis/issues/1287) _ocdav: api compatibility, return correct status code_ -- [apiAuthWebDav/webDavPOSTAuth.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPOSTAuth.feature#L40) Scenario: send POST requests to another user's webDav endpoints as normal user +- [apiAuthWebDav/webDavPOSTAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPOSTAuth.feature#L58) Scenario: send POST requests to another user's webDav endpoints as normal user #### [Using double slash in URL to access a folder gives 501 and other status codes](https://github.com/owncloud/ocis/issues/1667) -- [apiAuthWebDav/webDavSpecialURLs.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L24) -- [apiAuthWebDav/webDavSpecialURLs.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L69) -- [apiAuthWebDav/webDavSpecialURLs.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L91) +- [apiAuthWebDav/webDavSpecialURLs.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L34) +- [apiAuthWebDav/webDavSpecialURLs.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L121) +- [apiAuthWebDav/webDavSpecialURLs.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L163) #### [Default capabilities for normal user not same as in oC-core](https://github.com/owncloud/ocis/issues/1285) #### [Difference in response content of status.php and default capabilities](https://github.com/owncloud/ocis/issues/1286) @@ -989,8 +989,8 @@ Scenario Outline: A disabled user cannot use webdav - [apiAuth/cors.feature:185](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L185) #### [App Passwords/Tokens for legacy WebDAV clients](https://github.com/owncloud/ocis/issues/197) -- [apiAuthWebDav/webDavDELETEAuth.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L80) -- [apiAuthWebDav/webDavDELETEAuth.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L94) +- [apiAuthWebDav/webDavDELETEAuth.feature:136](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L136) +- [apiAuthWebDav/webDavDELETEAuth.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L162) #### [various sharing settings cannot be set](https://github.com/owncloud/ocis/issues/1328) - [apiCapabilities/capabilities.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L8) @@ -1292,4 +1292,14 @@ _ocs: api compatibility, return correct status code_ - [apiWebdavOperations/downloadFile.feature:170](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/downloadFile.feature#L170) ### [send PUT requests to another user's webDav endpoints as normal user](https://github.com/owncloud/ocis/issues/2893) -- [apiAuthWebDav/webDavPUTAuth.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPUTAuth.feature#L40) +- [apiAuthWebDav/webDavPUTAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPUTAuth.feature#L58) + +#### [Creating a new folder which is a substring of Shares leads to Unknown Error](https://github.com/owncloud/ocis/issues/3033) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L27) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L29) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L30) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L32) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L45) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L48) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L59) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L60) diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index ccb9054c4e5..109506e37ab 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -859,23 +859,23 @@ API, search, favorites, config, capabilities, not existing endpoints, CORS and o #### [Trying to access another user's file gives http 403 instead of 404](https://github.com/owncloud/ocis/issues/2175) _ocdav: api compatibility, return correct status code_ -- [apiAuthWebDav/webDavMKCOLAuth.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMKCOLAuth.feature#L36) Scenario: send MKCOL requests to another user's webDav endpoints as normal user +- [apiAuthWebDav/webDavMKCOLAuth.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMKCOLAuth.feature#L54) Scenario: send MKCOL requests to another user's webDav endpoints as normal user #### [trying to lock file of another user gives http 200](https://github.com/owncloud/ocis/issues/2176) -- [apiAuthWebDav/webDavLOCKAuth.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavLOCKAuth.feature#L40) Scenario: send LOCK requests to another user's webDav endpoints as normal user +- [apiAuthWebDav/webDavLOCKAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavLOCKAuth.feature#L58) Scenario: send LOCK requests to another user's webDav endpoints as normal user #### [Renaming a resource to banned name is allowed](https://github.com/owncloud/ocis/issues/1295) _ocdav: api compatibility, return correct status code_ -- [apiAuthWebDav/webDavMOVEAuth.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMOVEAuth.feature#L39) Scenario: send MOVE requests to another user's webDav endpoints as normal user +- [apiAuthWebDav/webDavMOVEAuth.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMOVEAuth.feature#L57) Scenario: send MOVE requests to another user's webDav endpoints as normal user #### [send POST requests to another user's webDav endpoints as normal user](https://github.com/owncloud/ocis/issues/1287) _ocdav: api compatibility, return correct status code_ -- [apiAuthWebDav/webDavPOSTAuth.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPOSTAuth.feature#L40) Scenario: send POST requests to another user's webDav endpoints as normal user +- [apiAuthWebDav/webDavPOSTAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPOSTAuth.feature#L58) Scenario: send POST requests to another user's webDav endpoints as normal user #### [Using double slash in URL to access a folder gives 501 and other status codes](https://github.com/owncloud/ocis/issues/1667) -- [apiAuthWebDav/webDavSpecialURLs.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L24) -- [apiAuthWebDav/webDavSpecialURLs.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L69) -- [apiAuthWebDav/webDavSpecialURLs.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L91) +- [apiAuthWebDav/webDavSpecialURLs.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L34) +- [apiAuthWebDav/webDavSpecialURLs.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L121) +- [apiAuthWebDav/webDavSpecialURLs.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L163) #### [Default capabilities for normal user not same as in oC-core](https://github.com/owncloud/ocis/issues/1285) #### [Difference in response content of status.php and default capabilities](https://github.com/owncloud/ocis/issues/1286) @@ -1007,8 +1007,8 @@ Scenario Outline: A disabled user cannot use webdav - [apiAuth/cors.feature:185](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L185) #### [App Passwords/Tokens for legacy WebDAV clients](https://github.com/owncloud/ocis/issues/197) -- [apiAuthWebDav/webDavDELETEAuth.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L80) -- [apiAuthWebDav/webDavDELETEAuth.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L94) +- [apiAuthWebDav/webDavDELETEAuth.feature:136](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L136) +- [apiAuthWebDav/webDavDELETEAuth.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L162) #### [various sharing settings cannot be set](https://github.com/owncloud/ocis/issues/1328) - [apiCapabilities/capabilities.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L8) @@ -1294,4 +1294,14 @@ _ocs: api compatibility, return correct status code_ - [apiWebdavOperations/downloadFile.feature:170](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/downloadFile.feature#L170) ### [send PUT requests to another user's webDav endpoints as normal user](https://github.com/owncloud/ocis/issues/2893) -- [apiAuthWebDav/webDavPUTAuth.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPUTAuth.feature#L40) +- [apiAuthWebDav/webDavPUTAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPUTAuth.feature#L58) + +#### [Creating a new folder which is a substring of Shares leads to Unknown Error](https://github.com/owncloud/ocis/issues/3033) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L27) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L29) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L30) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L32) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L45) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L48) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L59) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L60) \ No newline at end of file From c68537205646310ca7caa43c17c1357276e04f47 Mon Sep 17 00:00:00 2001 From: David Christofas Date: Thu, 27 Jan 2022 15:46:06 +0100 Subject: [PATCH 22/49] [tests-only] Merge master into edge (#2473) * [Build-deps] Additional rules for CODEOWNERS (#2323) * Remove share refs from trashbin (#2298) * Public link propfind (#2315) * fix public share type in propfinds (#2316) * Bump core commit id for tests (#2331) * Revert "Fix content disposition (#2303)" (#2332) This reverts commit 3cba22371b78213f2e49197c2783220331a264bd. * [Build-deps]: Bump github.com/gomodule/redigo from 1.8.5 to 1.8.6 (#2326) * [Build-deps]: Bump github.com/mitchellh/mapstructure from 1.4.2 to 1.4.3 (#2324) * [Build-deps]: Bump github.com/aws/aws-sdk-go from 1.42.9 to 1.42.19 (#2325) * fix app provider new file action and improve app provider error codes (#2210) * Parse URL path to determine file name (#2346) * v1.17.0 * handle non existent spaces gracefully (#2354) * Bump core commit id for tests (#2365) * [Build-deps]: Bump github.com/minio/minio-go/v7 from 7.0.16 to 7.0.18 (#2363) * [Build-deps]: Bump github.com/ReneKroon/ttlcache/v2 from 2.9.0 to 2.10.0 (#2358) * [Build-deps]: Bump go.opentelemetry.io/otel/exporters/jaeger (#2362) * fix tests by pointing to the right owncloud/core commit id for tests (#2375) * add new file capabilties to ocs for the app provider (#2379) * Remove test from expected to fail and bump commit id (#2380) * add .drone.env to CODEOWNERS as it is part of the test files (#2378) * fix webdav copy for zero byte files (#2374) * Implement touch file (#2369) * implement cs3org/cs3apis#154 * use TouchFile for the app provider * add changelog and comments * revert use TouchFile in app provider * fix resource typo Co-authored-by: Giuseppe Lo Presti Co-authored-by: Giuseppe Lo Presti * Dummy implementation of the Lock CS3APIs (#2350) * allow new file create with app provider on public links (#2385) * Bump core commit id and use core master for tests (#2391) * Add product to ocs Version struct (#2397) The web ui will announce the backend version in the javascript console and is supposed to include the product name as well. The version seems to be a good location for the product field as it already includes the software edition as well. * bump core commit id for tests (#2404) * [Build-deps]: Bump github.com/mattn/go-sqlite3 from 1.14.9 to 1.14.10 (#2409) * [Build-deps]: Bump github.com/minio/minio-go/v7 from 7.0.18 to 7.0.20 (#2408) * [Build-deps]: Bump github.com/rs/cors from 1.8.0 to 1.8.2 (#2399) * [Build-deps]: Bump github.com/ReneKroon/ttlcache/v2 (#2387) * [Build-deps]: Bump go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc (#2359) * [tests-only] format .drone.star (#2411) * update tus/tusd to version 1.8.0 (#2393) * Fixes for apps in public shares, project spaces for EOS driver (#2371) * [Build-deps]: Bump github.com/aws/aws-sdk-go from 1.42.19 to 1.42.27 (#2414) * [Build-deps]: Bump github.com/rs/zerolog from 1.26.0 to 1.26.1 (#2388) * update owncloud core commit id (#2418) * [Build-deps]: Bump github.com/mattn/go-sqlite3 (#2425) * [Build-deps]: Bump github.com/gomodule/redigo from 1.8.6 to 1.8.8 (#2426) * OIDC and WOPI changes for lightweight users (#2278) * [tests-only]Bump the commit id for tests (#2441) * Bump the commit id for tests * Adding failing tests to expected to failure * CephFS Reva v0.2 (#1209) * [Build-deps]: Bump github.com/minio/minio-go/v7 from 7.0.20 to 7.0.21 (#2449) * [Build-deps]: Bump github.com/hashicorp/go-hclog from 1.0.0 to 1.1.0 (#2448) * [Build-deps]: Bump github.com/BurntSushi/toml from 0.4.1 to 1.0.0 (#2446) * revert go-sqlite downgrade (#2461) * [Build-deps]: Bump github.com/google/go-cmp from 0.5.6 to 0.5.7 (#2466) * [Build-deps]: Bump github.com/aws/aws-sdk-go from 1.42.27 to 1.42.39 (#2467) * [Build-deps]: Bump github.com/ceph/go-ceph from 0.12.0 to 0.13.0 (#2468) * [Build-deps]: Bump github.com/onsi/gomega from 1.17.0 to 1.18.0 (#2469) * Use permissions API in decomposedfs (#2341) * [tests-only]Bump Core Commit Id (#2451) * Bump commit id for tests 2022-01-25 (#2474) * Bump commit id for issue-ocis-3030 (#2476) * fix test failures * fix integration tests Co-authored-by: Giuseppe Lo Presti Co-authored-by: Gianmaria Del Monte <39946305+gmgigi96@users.noreply.github.com> Co-authored-by: Swikriti Tripathi <41103328+SwikritiT@users.noreply.github.com> Co-authored-by: Ishank Arora Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Willy Kloucek <34452982+wkloucek@users.noreply.github.com> Co-authored-by: Michael Barz Co-authored-by: Phil Davis Co-authored-by: Benedikt Kulmann Co-authored-by: Saw-jan Gurung Co-authored-by: PKiran <39373750+kiranparajuli589@users.noreply.github.com> Co-authored-by: Mouratidis Theofilos Co-authored-by: Amrita <54478846+amrita-shrestha@users.noreply.github.com> --- .dockerignore | 1 - .drone.star | 105 ++++ Dockerfile.revad-ceph | 52 ++ Makefile | 12 +- changelog/unreleased/cephfs-driver.md | 3 + .../unreleased/cs3-permissions-service.md | 5 + cmd/revad/runtime/loader.go | 1 + .../packages/storage/fs/cephfs/_index.md | 26 + examples/ceph/ceph.conf | 3 + examples/ceph/keyring | 2 + go.mod | 20 +- go.sum | 44 +- .../grpc/services/gateway/authprovider.go | 2 + internal/grpc/services/gateway/gateway.go | 1 + internal/grpc/services/gateway/permissions.go | 37 ++ internal/grpc/services/loader/loader.go | 1 + .../grpc/services/permissions/permissions.go | 104 +++ .../storageprovider/storageprovider.go | 15 +- .../ocdav/propfind/mocks/GatewayClient.go | 34 +- .../owncloud/ocdav/spacelookup/spacelookup.go | 6 +- pkg/auth/scope/publicshare.go | 3 + pkg/permission/manager/demo/demo.go | 43 ++ pkg/permission/manager/loader/loader.go | 25 + pkg/permission/manager/registry/registry.go | 34 + pkg/permission/permission.go | 28 + pkg/rgrpc/todo/pool/pool.go | 21 + pkg/storage/fs/cephfs/cephfs.go | 591 ++++++++++++++++++ pkg/storage/fs/cephfs/chunking.go | 344 ++++++++++ pkg/storage/fs/cephfs/connections.go | 315 ++++++++++ pkg/storage/fs/cephfs/errors.go | 64 ++ pkg/storage/fs/cephfs/options.go | 90 +++ pkg/storage/fs/cephfs/permissions.go | 325 ++++++++++ pkg/storage/fs/cephfs/unsupported.go | 39 ++ pkg/storage/fs/cephfs/upload.go | 413 ++++++++++++ pkg/storage/fs/cephfs/user.go | 242 +++++++ pkg/storage/fs/cephfs/utils.go | 245 ++++++++ pkg/storage/fs/loader/loader.go | 1 + pkg/storage/fs/nextcloud/nextcloud.go | 2 +- pkg/storage/fs/nextcloud/nextcloud_test.go | 2 +- pkg/storage/fs/owncloud/owncloud.go | 2 +- pkg/storage/fs/owncloudsql/owncloudsql.go | 2 +- pkg/storage/fs/s3/s3.go | 2 +- pkg/storage/storage.go | 2 +- .../utils/decomposedfs/decomposedfs.go | 3 + .../utils/decomposedfs/options/options.go | 2 + pkg/storage/utils/decomposedfs/spaces.go | 34 +- pkg/storage/utils/eosfs/eosfs.go | 2 +- pkg/storage/utils/localfs/localfs.go | 2 +- .../expected-failures-on-S3NG-storage.md | 4 +- .../grpc/fixtures/gateway-sharded.toml | 1 + .../grpc/fixtures/gateway-static.toml | 1 + tests/integration/grpc/fixtures/gateway.toml | 1 + .../grpc/fixtures/permissions-ocis-ci.toml | 12 + .../grpc/fixtures/storageprovider-ocis.toml | 3 +- .../grpc/gateway_storageprovider_test.go | 22 +- tests/oc-integration-tests/drone/gateway.toml | 2 + .../drone/permissions-ocis-ci.toml | 12 + .../drone/storage-users-ocis.toml | 2 + tests/oc-integration-tests/local/gateway.toml | 2 + .../local/permissions-ocis-ci.toml | 12 + .../local/storage-users.toml | 1 + 61 files changed, 3360 insertions(+), 67 deletions(-) create mode 100644 Dockerfile.revad-ceph create mode 100644 changelog/unreleased/cephfs-driver.md create mode 100644 changelog/unreleased/cs3-permissions-service.md create mode 100644 docs/content/en/docs/config/packages/storage/fs/cephfs/_index.md create mode 100644 examples/ceph/ceph.conf create mode 100644 examples/ceph/keyring create mode 100644 internal/grpc/services/gateway/permissions.go create mode 100644 internal/grpc/services/permissions/permissions.go create mode 100644 pkg/permission/manager/demo/demo.go create mode 100644 pkg/permission/manager/loader/loader.go create mode 100644 pkg/permission/manager/registry/registry.go create mode 100644 pkg/permission/permission.go create mode 100644 pkg/storage/fs/cephfs/cephfs.go create mode 100644 pkg/storage/fs/cephfs/chunking.go create mode 100644 pkg/storage/fs/cephfs/connections.go create mode 100644 pkg/storage/fs/cephfs/errors.go create mode 100644 pkg/storage/fs/cephfs/options.go create mode 100644 pkg/storage/fs/cephfs/permissions.go create mode 100644 pkg/storage/fs/cephfs/unsupported.go create mode 100644 pkg/storage/fs/cephfs/upload.go create mode 100644 pkg/storage/fs/cephfs/user.go create mode 100644 pkg/storage/fs/cephfs/utils.go create mode 100644 tests/integration/grpc/fixtures/permissions-ocis-ci.toml create mode 100644 tests/oc-integration-tests/drone/permissions-ocis-ci.toml create mode 100644 tests/oc-integration-tests/local/permissions-ocis-ci.toml diff --git a/.dockerignore b/.dockerignore index 429a7e9d469..ab22e55d143 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,6 @@ .github changelog docs -examples grpc-tests tests tools diff --git a/.drone.star b/.drone.star index 163250822f4..d7aaffc2428 100644 --- a/.drone.star +++ b/.drone.star @@ -106,6 +106,8 @@ def main(ctx): litmusOcisOldWebdav(), litmusOcisNewWebdav(), litmusOcisSpacesDav(), + # virtual views don't work on edge at the moment + #virtualViews(), ] + ocisIntegrationTests(6) + s3ngIntegrationTests(12) def buildAndPublishDocker(): @@ -228,6 +230,26 @@ def buildAndPublishDocker(): ], }, }, + { + "name": "publish-docker-revad-ceph-latest", + "pull": "always", + "image": "plugins/docker", + "settings": { + "repo": "cs3org/revad", + "tags": "latest-ceph", + "dockerfile": "Dockerfile.revad-ceph", + "username": { + "from_secret": "dockerhub_username", + }, + "password": { + "from_secret": "dockerhub_password", + }, + "custom_dns": [ + "128.142.17.5", + "128.142.16.5", + ], + }, + }, ], } @@ -481,6 +503,84 @@ def release(): ], }, }, + { + "name": "docker-revad-ceph-tag", + "pull": "always", + "image": "plugins/docker", + "settings": { + "repo": "cs3org/revad", + "tags": "${DRONE_TAG}-ceph", + "dockerfile": "Dockerfile.revad-ceph", + "username": { + "from_secret": "dockerhub_username", + }, + "password": { + "from_secret": "dockerhub_password", + }, + "custom_dns": [ + "128.142.17.5", + "128.142.16.5", + ], + }, + }, + ], + "depends_on": ["changelog"], + } + +def virtualViews(): + return { + "kind": "pipeline", + "type": "docker", + "name": "virtual-views", + "platform": { + "os": "linux", + "arch": "amd64", + }, + "trigger": { + "event": { + "include": [ + "pull_request", + "tag", + ], + }, + }, + "steps": [ + makeStep("build-ci"), + { + "name": "revad-services", + "image": "registry.cern.ch/docker.io/library/golang:1.17", + "detach": True, + "commands": [ + "cd /drone/src/tests/oc-integration-tests/drone/", + "/drone/src/cmd/revad/revad -c frontend-global.toml &", + "/drone/src/cmd/revad/revad -c gateway.toml &", + "/drone/src/cmd/revad/revad -c storage-home-ocis.toml &", + "/drone/src/cmd/revad/revad -c storage-local-1.toml &", + "/drone/src/cmd/revad/revad -c storage-local-2.toml &", + "/drone/src/cmd/revad/revad -c users.toml", + ], + }, + cloneOc10TestReposStep(), + { + "name": "oC10APIAcceptanceTestsOcisStorage", + "image": "registry.cern.ch/docker.io/owncloudci/php:7.4", + "commands": [ + "cd /drone/src", + "make test-acceptance-api", + ], + "environment": { + "PATH_TO_CORE": "/drone/src/tmp/testrunner", + "TEST_SERVER_URL": "http://revad-services:20180", + "OCIS_REVA_DATA_ROOT": "/drone/src/tmp/reva/data/", + "DELETE_USER_DATA_CMD": "rm -rf /drone/src/tmp/reva/data/nodes/root/* /drone/src/tmp/reva/data/nodes/*-*-*-* /drone/src/tmp/reva/data/blobs/*", + "STORAGE_DRIVER": "OCIS", + "SKELETON_DIR": "/drone/src/tmp/testing/data/apiSkeleton", + "TEST_REVA": "true", + "REGULAR_USER_PASSWORD": "relativity", + "SEND_SCENARIO_LINE_REFERENCES": "true", + "BEHAT_SUITE": "apiVirtualViews", + }, + }, ], "depends_on": ["changelog"], } @@ -516,6 +616,7 @@ def litmusOcisOldWebdav(): "/drone/src/cmd/revad/revad -c storage-shares.toml &", "/drone/src/cmd/revad/revad -c storage-publiclink.toml &", "/drone/src/cmd/revad/revad -c shares.toml &", + "/drone/src/cmd/revad/revad -c permissions-ocis-ci.toml &", "/drone/src/cmd/revad/revad -c users.toml", ], }, @@ -571,6 +672,7 @@ def litmusOcisNewWebdav(): "/drone/src/cmd/revad/revad -c storage-shares.toml &", "/drone/src/cmd/revad/revad -c storage-publiclink.toml &", "/drone/src/cmd/revad/revad -c shares.toml &", + "/drone/src/cmd/revad/revad -c permissions-ocis-ci.toml &", "/drone/src/cmd/revad/revad -c users.toml", ], }, @@ -627,6 +729,7 @@ def litmusOcisSpacesDav(): "/drone/src/cmd/revad/revad -c storage-shares.toml &", "/drone/src/cmd/revad/revad -c storage-publiclink.toml &", "/drone/src/cmd/revad/revad -c shares.toml &", + "/drone/src/cmd/revad/revad -c permissions-ocis-ci.toml &", "/drone/src/cmd/revad/revad -c users.toml", ], }, @@ -695,6 +798,7 @@ def ocisIntegrationTests(parallelRuns, skipExceptParts = []): "/drone/src/cmd/revad/revad -c machine-auth.toml &", "/drone/src/cmd/revad/revad -c storage-users-ocis.toml &", "/drone/src/cmd/revad/revad -c storage-publiclink.toml &", + "/drone/src/cmd/revad/revad -c permissions-ocis-ci.toml &", "/drone/src/cmd/revad/revad -c ldap-users.toml", ], }, @@ -771,6 +875,7 @@ def s3ngIntegrationTests(parallelRuns, skipExceptParts = []): "/drone/src/cmd/revad/revad -c storage-publiclink.toml &", "/drone/src/cmd/revad/revad -c storage-shares.toml &", "/drone/src/cmd/revad/revad -c ldap-users.toml &", + "/drone/src/cmd/revad/revad -c permissions-ocis-ci.toml &", "/drone/src/cmd/revad/revad -c machine-auth.toml", ], }, diff --git a/Dockerfile.revad-ceph b/Dockerfile.revad-ceph new file mode 100644 index 00000000000..186acfcc63b --- /dev/null +++ b/Dockerfile.revad-ceph @@ -0,0 +1,52 @@ +# Copyright 2018-2021 CERN +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# In applying this license, CERN does not waive the privileges and immunities +# granted to it by virtue of its status as an Intergovernmental Organization +# or submit itself to any jurisdiction. + +FROM ceph/daemon-base + +RUN dnf update -y && dnf install -y \ + git \ + gcc \ + make \ + libcephfs-devel \ + librbd-devel \ + librados-devel + +ADD https://golang.org/dl/go1.16.4.linux-amd64.tar.gz \ + go1.16.4.linux-amd64.tar.gz + +RUN rm -rf /usr/local/go && \ + tar -C /usr/local -xzf go1.16.4.linux-amd64.tar.gz && \ + rm go1.16.4.linux-amd64.tar.gz + +ENV PATH /go/bin:/usr/local/go/bin:$PATH +ENV GOPATH /go + +WORKDIR /go/src/github/cs3org/reva +COPY . . +RUN mkdir -p /go/bin \ + make build-revad-cephfs-docker && \ + cp /go/src/github/cs3org/reva/cmd/revad/revad /usr/bin/revad + +RUN cp -r examples/ceph /etc/ + +RUN mkdir -p /etc/revad/ && echo "" > /etc/revad/revad.toml + +EXPOSE 9999 10000 + +ENTRYPOINT [ "/usr/bin/revad" ] +CMD [ "-c", "/etc/revad/revad.toml", "-p", "/var/run/revad.pid" ] diff --git a/Makefile b/Makefile index 0c737ea19b6..8d79e37919f 100644 --- a/Makefile +++ b/Makefile @@ -30,10 +30,9 @@ off: imports: off `go env GOPATH`/bin/goimports -w tools pkg internal cmd -build: imports test-go-version - gofmt -s -w . - go build -ldflags ${BUILD_FLAGS} -o ./cmd/revad/revad ./cmd/revad - go build -ldflags ${BUILD_FLAGS} -o ./cmd/reva/reva ./cmd/reva +build: build-revad build-reva test-go-version + +build-cephfs: build-revad-cephfs build-reva tidy: go mod tidy @@ -41,6 +40,9 @@ tidy: build-revad: imports go build -ldflags ${BUILD_FLAGS} -o ./cmd/revad/revad ./cmd/revad +build-revad-cephfs: imports + go build -ldflags ${BUILD_FLAGS} -tags ceph -o ./cmd/revad/revad ./cmd/revad + build-reva: imports go build -ldflags ${BUILD_FLAGS} -o ./cmd/reva/reva ./cmd/reva @@ -104,6 +106,8 @@ ci: build-ci test lint-ci # to be run in Docker build build-revad-docker: off go build -ldflags ${BUILD_FLAGS} -o ./cmd/revad/revad ./cmd/revad +build-revad-cephfs-docker: off + go build -ldflags ${BUILD_FLAGS} -tags ceph -o ./cmd/revad/revad ./cmd/revad build-reva-docker: off go build -ldflags ${BUILD_FLAGS} -o ./cmd/reva/reva ./cmd/reva clean: diff --git a/changelog/unreleased/cephfs-driver.md b/changelog/unreleased/cephfs-driver.md new file mode 100644 index 00000000000..9015c61538f --- /dev/null +++ b/changelog/unreleased/cephfs-driver.md @@ -0,0 +1,3 @@ +Enhancement: Reva CephFS module v0.2.1 + +https://github.com/cs3org/reva/pull/1209 \ No newline at end of file diff --git a/changelog/unreleased/cs3-permissions-service.md b/changelog/unreleased/cs3-permissions-service.md new file mode 100644 index 00000000000..b792173e209 --- /dev/null +++ b/changelog/unreleased/cs3-permissions-service.md @@ -0,0 +1,5 @@ +Enhancement: Use CS3 permissions API + +Added calls to the CS3 permissions API to the decomposedfs in order to check the user permissions. + +https://github.com/cs3org/reva/pull/2341 diff --git a/cmd/revad/runtime/loader.go b/cmd/revad/runtime/loader.go index 93f5c68acee..a0df6920276 100644 --- a/cmd/revad/runtime/loader.go +++ b/cmd/revad/runtime/loader.go @@ -38,6 +38,7 @@ import ( _ "github.com/cs3org/reva/pkg/ocm/invite/manager/loader" _ "github.com/cs3org/reva/pkg/ocm/provider/authorizer/loader" _ "github.com/cs3org/reva/pkg/ocm/share/manager/loader" + _ "github.com/cs3org/reva/pkg/permission/manager/loader" _ "github.com/cs3org/reva/pkg/publicshare/manager/loader" _ "github.com/cs3org/reva/pkg/rhttp/datatx/manager/loader" _ "github.com/cs3org/reva/pkg/share/cache/loader" diff --git a/docs/content/en/docs/config/packages/storage/fs/cephfs/_index.md b/docs/content/en/docs/config/packages/storage/fs/cephfs/_index.md new file mode 100644 index 00000000000..48dc231ebf3 --- /dev/null +++ b/docs/content/en/docs/config/packages/storage/fs/cephfs/_index.md @@ -0,0 +1,26 @@ +--- +title: "cephfs" +linkTitle: "cephfs" +weight: 10 +description: > + Configuration for the cephfs service +--- + +# _struct: config_ + +{{% dir name="root" type="string" default="/var/tmp/reva/" %}} +Path of root directory for user storage. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/storage/fs/cephfs/cephfs.go#L34) +{{< highlight toml >}} +[storage.fs.cephfs] +root = "/var/tmp/reva/" +{{< /highlight >}} +{{% /dir %}} + +{{% dir name="share_folder" type="string" default="/MyShares" %}} +Path for storing share references. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/storage/fs/cephfs/cephfs.go#L35) +{{< highlight toml >}} +[storage.fs.cephfs] +share_folder = "/MyShares" +{{< /highlight >}} +{{% /dir %}} + diff --git a/examples/ceph/ceph.conf b/examples/ceph/ceph.conf new file mode 100644 index 00000000000..f2daaa8de56 --- /dev/null +++ b/examples/ceph/ceph.conf @@ -0,0 +1,3 @@ +[global] + fsid = '8aaa35c4-75dc-42e5-a812-cbc1cdfd3323' + mon_host = '[v2:188.184.96.178:3300/0,v1:188.184.96.178:6789/0] [v2:188.185.88.76:3300/0,v1:188.185.88.76:6789/0] [v2:188.185.126.6:3300/0,v1:188.185.126.6:6789/0]' diff --git a/examples/ceph/keyring b/examples/ceph/keyring new file mode 100644 index 00000000000..9e555cc1b2f --- /dev/null +++ b/examples/ceph/keyring @@ -0,0 +1,2 @@ +[client.admin] + key = 'AQAu88Fg5iekGhAAeVP0Td05PuybytuRJgBRqA==' diff --git a/go.mod b/go.mod index 5facff17239..d1fb1c322d2 100644 --- a/go.mod +++ b/go.mod @@ -3,21 +3,23 @@ module github.com/cs3org/reva require ( bou.ke/monkey v1.0.2 contrib.go.opencensus.io/exporter/prometheus v0.4.0 - github.com/BurntSushi/toml v0.4.1 + github.com/BurntSushi/toml v1.0.0 github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/sprig v2.22.0+incompatible github.com/ReneKroon/ttlcache/v2 v2.11.0 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect - github.com/aws/aws-sdk-go v1.42.27 + github.com/aws/aws-sdk-go v1.42.39 github.com/beevik/etree v1.1.0 github.com/bluele/gcache v0.0.2 github.com/c-bata/go-prompt v0.2.5 + github.com/ceph/go-ceph v0.13.0 github.com/cheggaaa/pb v1.0.29 github.com/coreos/go-oidc v2.2.1+incompatible github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e - github.com/cs3org/go-cs3apis v0.0.0-20211214102047-7ce3134d7bf8 + github.com/cs3org/go-cs3apis v0.0.0-20211214102128-4e8745ab1654 github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 + github.com/dgraph-io/ristretto v0.1.0 github.com/eventials/go-tus v0.0.0-20200718001131-45c7ec8f5d59 github.com/gdexlab/go-render v1.0.1 github.com/go-chi/chi/v5 v5.0.7 @@ -28,25 +30,26 @@ require ( github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang/protobuf v1.5.2 github.com/gomodule/redigo v1.8.8 - github.com/google/go-cmp v0.5.6 + github.com/google/go-cmp v0.5.7 github.com/google/go-github v17.0.0+incompatible github.com/google/go-querystring v1.1.0 // indirect github.com/google/uuid v1.3.0 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 - github.com/hashicorp/go-hclog v1.0.0 + github.com/hashicorp/go-hclog v1.1.0 github.com/hashicorp/go-plugin v1.4.3 github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/jedib0t/go-pretty v4.3.0+incompatible github.com/juliangruber/go-intersect v1.1.0 github.com/mattn/go-sqlite3 v1.14.10 + github.com/maxymania/go-system v0.0.0-20170110133659-647cc364bf0b github.com/mileusna/useragent v1.0.2 - github.com/minio/minio-go/v7 v7.0.20 + github.com/minio/minio-go/v7 v7.0.21 github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/mapstructure v1.4.3 github.com/onsi/ginkgo v1.16.5 - github.com/onsi/gomega v1.17.0 + github.com/onsi/gomega v1.18.0 github.com/pkg/errors v0.9.1 github.com/pkg/xattr v0.4.4 github.com/pquerna/cachecontrol v0.1.0 // indirect @@ -71,7 +74,8 @@ require ( go.opentelemetry.io/otel/trace v1.3.0 golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 - golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e golang.org/x/term v0.0.0-20210916214954-140adaaadfaf google.golang.org/genproto v0.0.0-20211021150943-2b146023228c google.golang.org/grpc v1.42.0 diff --git a/go.sum b/go.sum index b061177b681..56a3789f7b4 100644 --- a/go.sum +++ b/go.sum @@ -60,8 +60,8 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= -github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= +github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= @@ -96,11 +96,12 @@ github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.20.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= +github.com/aws/aws-sdk-go v1.35.24/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.40.11/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go v1.41.13/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= -github.com/aws/aws-sdk-go v1.42.27 h1:kxsBXQg3ee6LLbqjp5/oUeDgG7TENFrWYDmEVnd7spU= -github.com/aws/aws-sdk-go v1.42.27/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc= +github.com/aws/aws-sdk-go v1.42.39 h1:6Lso73VoCI8Zmv3zAMv4BNg2gHAKNOlbLv1s/ew90SI= +github.com/aws/aws-sdk-go v1.42.39/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc= github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -118,6 +119,8 @@ github.com/c-bata/go-prompt v0.2.5 h1:3zg6PecEywxNn0xiqcXHD96fkbxghD+gdB2tbsYfl+ github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/ceph/go-ceph v0.13.0 h1:69dgIPlNHD2OCz98T0benI4++vcnShGcpQK4RIALjw4= +github.com/ceph/go-ceph v0.13.0/go.mod h1:mafFpf5Vg8Ai8Bd+FAMvKBHLmtdpTXdRP/TNq8XWegY= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= @@ -133,13 +136,17 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e h1:tqSPWQeueWTKnJVMJffz4pz0o1WuQxJ28+5x5JgaHD8= github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e/go.mod h1:XJEZ3/EQuI3BXTp/6DUzFr850vlxq11I6satRtz0YQ4= -github.com/cs3org/go-cs3apis v0.0.0-20211214102047-7ce3134d7bf8 h1:PqOprF37OvwCbAN5W23znknGk6N/LMayqLAeP904FHE= -github.com/cs3org/go-cs3apis v0.0.0-20211214102047-7ce3134d7bf8/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= +github.com/cs3org/go-cs3apis v0.0.0-20211214102128-4e8745ab1654 h1:ha5tiuuFyDrwKUrVEc3TrRDFgTKVQ9NGDRmEP0PRPno= +github.com/cs3org/go-cs3apis v0.0.0-20211214102128-4e8745ab1654/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 h1:Z9lwXumT5ACSmJ7WGnFl+OMLLjpz5uR2fyz7dC255FI= github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8/go.mod h1:4abs/jPXcmJzYoYGF91JF9Uq9s/KL5n1jvFDix8KcqY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= +github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= @@ -301,6 +308,8 @@ github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGt github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= @@ -360,8 +369,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= @@ -382,6 +392,7 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -403,8 +414,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2 github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.0.0 h1:bkKf0BeBXcSYa7f5Fyi9gMuQ8gNsxeiNpZjR6VxNZeo= -github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.1.0 h1:QsGcniKx5/LuX2eYoeL+Np3UKYPNaN7YKpTh29h8rbw= +github.com/hashicorp/go-hclog v1.1.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= @@ -510,13 +521,15 @@ github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI= github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/maxymania/go-system v0.0.0-20170110133659-647cc364bf0b h1:Q53idHrTuQDDHyXaxZ6pUl0I9uyD6Z6uKFK3ocX6LzI= +github.com/maxymania/go-system v0.0.0-20170110133659-647cc364bf0b/go.mod h1:KirJrATYGbTyUwVR26xIkaipRqRcMRXBf8N5dacvGus= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/mileusna/useragent v1.0.2 h1:DgVKtiPnjxlb73z9bCwgdUvU2nQNQ97uhgfO8l9uz/w= github.com/mileusna/useragent v1.0.2/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc= github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4= github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw= -github.com/minio/minio-go/v7 v7.0.20 h1:0+Xt1SkCKDgcx5cmo3UxXcJ37u5Gy+/2i/+eQYqmYJw= -github.com/minio/minio-go/v7 v7.0.20/go.mod h1:ei5JjmxwHaMrgsMrn4U/+Nmg+d8MKS1U2DAn1ou4+Do= +github.com/minio/minio-go/v7 v7.0.21 h1:xrc4BQr1Fa4s5RwY0xfMjPZFJ1bcYBCCHYlngBdWV+k= +github.com/minio/minio-go/v7 v7.0.21/go.mod h1:ei5JjmxwHaMrgsMrn4U/+Nmg+d8MKS1U2DAn1ou4+Do= github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -563,11 +576,14 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.0 h1:ngbYoRctxjl8SiF7XgP0NxBFbfHcg3wfHMMaFHWwMTM= +github.com/onsi/gomega v1.18.0/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -910,6 +926,7 @@ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -947,8 +964,9 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 h1:2B5p2L5IfGiD7+b9BOoRMC6DgObAVZV+Fsp050NqXik= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210916214954-140adaaadfaf h1:Ihq/mm/suC88gF8WFcVwk+OV6Tq+wyA1O0E5UEvDglI= diff --git a/internal/grpc/services/gateway/authprovider.go b/internal/grpc/services/gateway/authprovider.go index 2f066797ddc..ed8357ab7fb 100644 --- a/internal/grpc/services/gateway/authprovider.go +++ b/internal/grpc/services/gateway/authprovider.go @@ -128,6 +128,8 @@ func (s *svc) Authenticate(ctx context.Context, req *gateway.AuthenticateRequest }, nil } + // scope := res.TokenScope + token, err = s.tokenmgr.MintToken(ctx, &u, scope) if err != nil { err = errors.Wrap(err, "authsvc: error in MintToken") diff --git a/internal/grpc/services/gateway/gateway.go b/internal/grpc/services/gateway/gateway.go index 43e8aa5bc9a..d5afca23b41 100644 --- a/internal/grpc/services/gateway/gateway.go +++ b/internal/grpc/services/gateway/gateway.go @@ -55,6 +55,7 @@ type config struct { GroupProviderEndpoint string `mapstructure:"groupprovidersvc"` DataTxEndpoint string `mapstructure:"datatx"` DataGatewayEndpoint string `mapstructure:"datagateway"` + PermissionsEndpoint string `mapstructure:"permissionssvc"` CommitShareToStorageGrant bool `mapstructure:"commit_share_to_storage_grant"` CommitShareToStorageRef bool `mapstructure:"commit_share_to_storage_ref"` DisableHomeCreationOnLogin bool `mapstructure:"disable_home_creation_on_login"` diff --git a/internal/grpc/services/gateway/permissions.go b/internal/grpc/services/gateway/permissions.go new file mode 100644 index 00000000000..537f614031a --- /dev/null +++ b/internal/grpc/services/gateway/permissions.go @@ -0,0 +1,37 @@ +// Copyright 2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package gateway + +import ( + "context" + + permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" + "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" +) + +func (s *svc) CheckPermission(ctx context.Context, req *permissions.CheckPermissionRequest) (*permissions.CheckPermissionResponse, error) { + c, err := pool.GetPermissionsClient(s.c.PermissionsEndpoint) + if err != nil { + return &permissions.CheckPermissionResponse{ + Status: status.NewInternal(ctx, "error getting permissions client"), + }, nil + } + return c.CheckPermission(ctx, req) +} diff --git a/internal/grpc/services/loader/loader.go b/internal/grpc/services/loader/loader.go index 4b72259cdff..b28fbdec5c5 100644 --- a/internal/grpc/services/loader/loader.go +++ b/internal/grpc/services/loader/loader.go @@ -33,6 +33,7 @@ import ( _ "github.com/cs3org/reva/internal/grpc/services/ocminvitemanager" _ "github.com/cs3org/reva/internal/grpc/services/ocmproviderauthorizer" _ "github.com/cs3org/reva/internal/grpc/services/ocmshareprovider" + _ "github.com/cs3org/reva/internal/grpc/services/permissions" _ "github.com/cs3org/reva/internal/grpc/services/preferences" _ "github.com/cs3org/reva/internal/grpc/services/publicshareprovider" _ "github.com/cs3org/reva/internal/grpc/services/publicstorageprovider" diff --git a/internal/grpc/services/permissions/permissions.go b/internal/grpc/services/permissions/permissions.go new file mode 100644 index 00000000000..4479fdb88c6 --- /dev/null +++ b/internal/grpc/services/permissions/permissions.go @@ -0,0 +1,104 @@ +// Copyright 2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package permissions + +import ( + "context" + "fmt" + + permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + "github.com/cs3org/reva/pkg/permission" + "github.com/cs3org/reva/pkg/permission/manager/registry" + "github.com/cs3org/reva/pkg/rgrpc" + "github.com/mitchellh/mapstructure" + "github.com/pkg/errors" + "google.golang.org/grpc" +) + +func init() { + rgrpc.Register("permissions", New) +} + +type config struct { + Driver string `mapstructure:"driver" docs:"localhome;The permission driver to be used."` + Drivers map[string]map[string]interface{} `mapstructure:"drivers" docs:"url:pkg/permission/permission.go"` +} + +func parseConfig(m map[string]interface{}) (*config, error) { + c := &config{} + if err := mapstructure.Decode(m, c); err != nil { + err = errors.Wrap(err, "error decoding conf") + return nil, err + } + return c, nil +} + +type service struct { + manager permission.Manager +} + +// New returns a new PermissionsServiceServer +func New(m map[string]interface{}, ss *grpc.Server) (rgrpc.Service, error) { + c, err := parseConfig(m) + if err != nil { + return nil, err + } + + f, ok := registry.NewFuncs[c.Driver] + if !ok { + return nil, fmt.Errorf("could not get permission manager '%s'", c.Driver) + } + manager, err := f(c.Drivers[c.Driver]) + if err != nil { + return nil, err + } + + service := &service{manager: manager} + return service, nil +} + +func (s *service) Close() error { + return nil +} + +func (s *service) UnprotectedEndpoints() []string { + return []string{} +} + +func (s *service) Register(ss *grpc.Server) { + permissions.RegisterPermissionsAPIServer(ss, s) +} + +func (s *service) CheckPermission(ctx context.Context, req *permissions.CheckPermissionRequest) (*permissions.CheckPermissionResponse, error) { + var subject string + switch ref := req.SubjectRef.Spec.(type) { + case *permissions.SubjectReference_UserId: + subject = ref.UserId.OpaqueId + case *permissions.SubjectReference_GroupId: + subject = ref.GroupId.OpaqueId + } + var status *rpc.Status + if ok := s.manager.CheckPermission(req.Permission, subject, req.Ref); ok { + status = &rpc.Status{Code: rpc.Code_CODE_OK} + } else { + status = &rpc.Status{Code: rpc.Code_CODE_PERMISSION_DENIED} + } + return &permissions.CheckPermissionResponse{Status: status}, nil +} diff --git a/internal/grpc/services/storageprovider/storageprovider.go b/internal/grpc/services/storageprovider/storageprovider.go index e1f0505b8b3..6815669964a 100644 --- a/internal/grpc/services/storageprovider/storageprovider.go +++ b/internal/grpc/services/storageprovider/storageprovider.go @@ -20,7 +20,6 @@ package storageprovider import ( "context" - "encoding/json" "fmt" "net/url" "os" @@ -525,19 +524,7 @@ func (s *service) CreateStorageSpace(ctx context.Context, req *provider.CreateSt func (s *service) ListStorageSpaces(ctx context.Context, req *provider.ListStorageSpacesRequest) (*provider.ListStorageSpacesResponse, error) { log := appctx.GetLogger(ctx) - // This is just a quick hack to get the users permission into reva. - // Replace this as soon as we have a proper system to check the users permissions. - opaque := req.Opaque - var permissions map[string]struct{} - if opaque != nil { - entry := opaque.Map["permissions"] - err := json.Unmarshal(entry.Value, &permissions) - if err != nil { - return nil, err - } - } - - spaces, err := s.storage.ListStorageSpaces(ctx, req.Filters, permissions) + spaces, err := s.storage.ListStorageSpaces(ctx, req.Filters) if err != nil { var st *rpc.Status switch err.(type) { diff --git a/internal/http/services/owncloud/ocdav/propfind/mocks/GatewayClient.go b/internal/http/services/owncloud/ocdav/propfind/mocks/GatewayClient.go index 9d03625ddb1..38568995662 100644 --- a/internal/http/services/owncloud/ocdav/propfind/mocks/GatewayClient.go +++ b/internal/http/services/owncloud/ocdav/propfind/mocks/GatewayClient.go @@ -16,7 +16,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -// Code generated by mockery v1.0.0. DO NOT EDIT. +// Code generated by mockery v120.0. DO NOT EDIT. package mocks @@ -46,6 +46,8 @@ import ( ocmv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1" + permissionsv1beta1 "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" + preferencesv1beta1 "github.com/cs3org/go-cs3apis/cs3/preferences/v1beta1" providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" @@ -184,6 +186,36 @@ func (_m *GatewayClient) CancelTransfer(ctx context.Context, in *txv1beta1.Cance return r0, r1 } +// CheckPermission provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) CheckPermission(ctx context.Context, in *permissionsv1beta1.CheckPermissionRequest, opts ...grpc.CallOption) (*permissionsv1beta1.CheckPermissionResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *permissionsv1beta1.CheckPermissionResponse + if rf, ok := ret.Get(0).(func(context.Context, *permissionsv1beta1.CheckPermissionRequest, ...grpc.CallOption) *permissionsv1beta1.CheckPermissionResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*permissionsv1beta1.CheckPermissionResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *permissionsv1beta1.CheckPermissionRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // CreateContainer provides a mock function with given fields: ctx, in, opts func (_m *GatewayClient) CreateContainer(ctx context.Context, in *providerv1beta1.CreateContainerRequest, opts ...grpc.CallOption) (*providerv1beta1.CreateContainerResponse, error) { _va := make([]interface{}, len(opts)) diff --git a/internal/http/services/owncloud/ocdav/spacelookup/spacelookup.go b/internal/http/services/owncloud/ocdav/spacelookup/spacelookup.go index 3be319bec29..b97b18aee02 100644 --- a/internal/http/services/owncloud/ocdav/spacelookup/spacelookup.go +++ b/internal/http/services/owncloud/ocdav/spacelookup/spacelookup.go @@ -57,7 +57,11 @@ func LookUpStorageSpaceForPath(ctx context.Context, client gateway.GatewayAPICli lSSRes, err := client.ListStorageSpaces(ctx, lSSReq) if err != nil || lSSRes.Status.Code != rpc.Code_CODE_OK { - return nil, lSSRes.Status, err + status := status.NewStatusFromErrType(ctx, "failed to lookup storage spaces", err) + if lSSRes != nil { + status = lSSRes.Status + } + return nil, status, err } switch len(lSSRes.StorageSpaces) { case 0: diff --git a/pkg/auth/scope/publicshare.go b/pkg/auth/scope/publicshare.go index bd52bbfea37..0ca65d87297 100644 --- a/pkg/auth/scope/publicshare.go +++ b/pkg/auth/scope/publicshare.go @@ -27,6 +27,7 @@ import ( authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + permissionsv1beta1 "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1" @@ -81,6 +82,8 @@ func publicshareScope(ctx context.Context, scope *authpb.Scope, resource interfa return checkStorageRef(ctx, &share, &provider.Reference{ResourceId: v.ResourceInfo.Id}), nil case *gateway.OpenInAppRequest: return checkStorageRef(ctx, &share, v.GetRef()), nil + case *permissionsv1beta1.CheckPermissionRequest: + return true, nil // Editor role // need to return appropriate status codes in the ocs/ocdav layers. diff --git a/pkg/permission/manager/demo/demo.go b/pkg/permission/manager/demo/demo.go new file mode 100644 index 00000000000..eb9da7544dc --- /dev/null +++ b/pkg/permission/manager/demo/demo.go @@ -0,0 +1,43 @@ +// Copyright 2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package demo + +import ( + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/pkg/permission" + "github.com/cs3org/reva/pkg/permission/manager/registry" +) + +func init() { + registry.Register("demo", New) +} + +// New returns a new demo permission manager +func New(c map[string]interface{}) (permission.Manager, error) { + return manager{}, nil +} + +type manager struct { +} + +func (m manager) CheckPermission(permission string, subject string, ref *provider.Reference) bool { + // We can currently return true all the time. + // Once we beginn testing roles we need to somehow check the roles of the users here + return false +} diff --git a/pkg/permission/manager/loader/loader.go b/pkg/permission/manager/loader/loader.go new file mode 100644 index 00000000000..5f0bbc5774b --- /dev/null +++ b/pkg/permission/manager/loader/loader.go @@ -0,0 +1,25 @@ +// Copyright 2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package loader + +import ( + // Load permission manager drivers + _ "github.com/cs3org/reva/pkg/permission/manager/demo" + // Add your own here +) diff --git a/pkg/permission/manager/registry/registry.go b/pkg/permission/manager/registry/registry.go new file mode 100644 index 00000000000..26f55bebade --- /dev/null +++ b/pkg/permission/manager/registry/registry.go @@ -0,0 +1,34 @@ +// Copyright 2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package registry + +import "github.com/cs3org/reva/pkg/permission" + +// NewFunc is the function that permission managers +// should register at init time. +type NewFunc func(map[string]interface{}) (permission.Manager, error) + +// NewFuncs is a map containing all the registered share managers. +var NewFuncs = map[string]NewFunc{} + +// Register registers a new permission manager new function. +// Not safe for concurrent use. Safe for use from package init. +func Register(name string, f NewFunc) { + NewFuncs[name] = f +} diff --git a/pkg/permission/permission.go b/pkg/permission/permission.go new file mode 100644 index 00000000000..e5e5c76a528 --- /dev/null +++ b/pkg/permission/permission.go @@ -0,0 +1,28 @@ +// Copyright 2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package permission + +import ( + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" +) + +// Manager defines the interface for the permission service driver +type Manager interface { + CheckPermission(permission string, subject string, ref *provider.Reference) bool +} diff --git a/pkg/rgrpc/todo/pool/pool.go b/pkg/rgrpc/todo/pool/pool.go index 550d7d7499a..b2c94b1219b 100644 --- a/pkg/rgrpc/todo/pool/pool.go +++ b/pkg/rgrpc/todo/pool/pool.go @@ -32,6 +32,7 @@ import ( ocmcore "github.com/cs3org/go-cs3apis/cs3/ocm/core/v1beta1" invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1" ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" + permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" preferences "github.com/cs3org/go-cs3apis/cs3/preferences/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" @@ -70,6 +71,7 @@ var ( ocmCores = newProvider() publicShareProviders = newProvider() preferencesProviders = newProvider() + permissionsProviders = newProvider() appRegistries = newProvider() appProviders = newProvider() storageRegistries = newProvider() @@ -349,6 +351,25 @@ func GetPreferencesClient(endpoint string) (preferences.PreferencesAPIClient, er return v, nil } +// GetPermissionsClient returns a new PermissionsClient. +func GetPermissionsClient(endpoint string) (permissions.PermissionsAPIClient, error) { + permissionsProviders.m.Lock() + defer permissionsProviders.m.Unlock() + + if c, ok := permissionsProviders.conn[endpoint]; ok { + return c.(permissions.PermissionsAPIClient), nil + } + + conn, err := NewConn(endpoint) + if err != nil { + return nil, err + } + + v := permissions.NewPermissionsAPIClient(conn) + permissionsProviders.conn[endpoint] = v + return v, nil +} + // GetAppRegistryClient returns a new AppRegistryClient. func GetAppRegistryClient(endpoint string) (appregistry.RegistryAPIClient, error) { appRegistries.m.Lock() diff --git a/pkg/storage/fs/cephfs/cephfs.go b/pkg/storage/fs/cephfs/cephfs.go new file mode 100644 index 00000000000..e71db2df755 --- /dev/null +++ b/pkg/storage/fs/cephfs/cephfs.go @@ -0,0 +1,591 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +//go:build ceph +// +build ceph + +package cephfs + +import ( + "context" + "fmt" + "io" + "net/url" + "os" + "path/filepath" + "strconv" + "strings" + + cephfs2 "github.com/ceph/go-ceph/cephfs" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/storage" + "github.com/cs3org/reva/pkg/storage/fs/registry" + "github.com/mitchellh/mapstructure" + "github.com/pkg/errors" +) + +const ( + xattrTrustedNs = "trusted." + xattrEID = xattrTrustedNs + "eid" + xattrMd5 = xattrTrustedNs + "checksum" + xattrMd5ts = xattrTrustedNs + "checksumTS" + xattrRef = xattrTrustedNs + "ref" + xattrUserNs = "user." + snap = ".snap" +) + +type cephfs struct { + conf *Options + conn *connections + adminConn *adminConn + chunkHandler *ChunkHandler +} + +func init() { + registry.Register("cephfs", New) +} + +// New returns an implementation to of the storage.FS interface that talk to +// a ceph filesystem. +func New(m map[string]interface{}) (fs storage.FS, err error) { + c := &Options{} + if err = mapstructure.Decode(m, c); err != nil { + return nil, errors.Wrap(err, "error decoding conf") + } + + c.fillDefaults() + + var cache *connections + if cache, err = newCache(); err != nil { + return nil, errors.New("cephfs: can't create caches") + } + + adminConn := newAdminConn(c.IndexPool) + if adminConn == nil { + return nil, errors.Wrap(err, "cephfs: Couldn't create admin connections") + } + + for _, dir := range []string{c.ShadowFolder, c.UploadFolder} { + err = adminConn.adminMount.MakeDir(dir, dirPermFull) + if err != nil && err.Error() != errFileExists { + return nil, errors.New("cephfs: can't initialise system dir " + dir + ":" + err.Error()) + } + } + + return &cephfs{ + conf: c, + conn: cache, + adminConn: adminConn, + }, nil +} + +func (fs *cephfs) GetHome(ctx context.Context) (string, error) { + if fs.conf.DisableHome { + return "", errtypes.NotSupported("cephfs: GetHome() home supported disabled") + } + + user := fs.makeUser(ctx) + + return user.home, nil +} + +func (fs *cephfs) CreateHome(ctx context.Context) (err error) { + if fs.conf.DisableHome { + return errtypes.NotSupported("cephfs: GetHome() home supported disabled") + } + + user := fs.makeUser(ctx) + + // Stop createhome from running the whole thing because it is called multiple times + if _, err = fs.adminConn.adminMount.Statx(user.home, cephfs2.StatxMode, 0); err == nil { + return + } + + err = walkPath(user.home, func(path string) error { + return fs.adminConn.adminMount.MakeDir(path, dirPermDefault) + }, false) + if err != nil { + return getRevaError(err) + } + + err = fs.adminConn.adminMount.Chown(user.home, uint32(user.UidNumber), uint32(user.GidNumber)) + if err != nil { + return getRevaError(err) + } + + err = fs.adminConn.adminMount.SetXattr(user.home, "ceph.quota.max_bytes", []byte(fmt.Sprint(fs.conf.UserQuotaBytes)), 0) + if err != nil { + return getRevaError(err) + } + + user.op(func(cv *cacheVal) { + err = cv.mount.MakeDir(removeLeadingSlash(fs.conf.ShareFolder), dirPermDefault) + if err != nil && err.Error() == errFileExists { + err = nil + } + }) + + return getRevaError(err) +} + +func (fs *cephfs) CreateDir(ctx context.Context, ref *provider.Reference) error { + user := fs.makeUser(ctx) + path, err := user.resolveRef(ref) + if err != nil { + return getRevaError(err) + } + + user.op(func(cv *cacheVal) { + if err = cv.mount.MakeDir(path, dirPermDefault); err != nil { + return + } + + //TODO(tmourati): Add entry id logic + }) + + return getRevaError(err) +} + +func (fs *cephfs) Delete(ctx context.Context, ref *provider.Reference) (err error) { + var path string + user := fs.makeUser(ctx) + path, err = user.resolveRef(ref) + if err != nil { + return err + } + + user.op(func(cv *cacheVal) { + if err = cv.mount.Unlink(path); err != nil && err.Error() == errIsADirectory { + err = cv.mount.RemoveDir(path) + } + + //TODO(tmourati): Add entry id logic + }) + + //has already been deleted by direct mount + if err != nil && err.Error() == errNotFound { + return nil + } + + return getRevaError(err) +} + +func (fs *cephfs) Move(ctx context.Context, oldRef, newRef *provider.Reference) (err error) { + var oldPath, newPath string + user := fs.makeUser(ctx) + if oldPath, err = user.resolveRef(oldRef); err != nil { + return + } + if newPath, err = user.resolveRef(newRef); err != nil { + return + } + + user.op(func(cv *cacheVal) { + if err = cv.mount.Rename(oldPath, newPath); err != nil { + return + } + + //TODO(tmourati): Add entry id logic, handle already moved file error + }) + + // has already been moved by direct mount + if err != nil && err.Error() == errNotFound { + return nil + } + + return getRevaError(err) +} + +func (fs *cephfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []string) (ri *provider.ResourceInfo, err error) { + var path string + user := fs.makeUser(ctx) + + if path, err = user.resolveRef(ref); err != nil { + return nil, err + } + + user.op(func(cv *cacheVal) { + var stat Statx + if stat, err = cv.mount.Statx(path, cephfs2.StatxBasicStats, 0); err != nil { + return + } + ri, err = user.fileAsResourceInfo(cv, path, stat, mdKeys) + }) + + return ri, getRevaError(err) +} + +func (fs *cephfs) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys []string) (files []*provider.ResourceInfo, err error) { + var path string + user := fs.makeUser(ctx) + if path, err = user.resolveRef(ref); err != nil { + return + } + + user.op(func(cv *cacheVal) { + var dir *cephfs2.Directory + if dir, err = cv.mount.OpenDir(path); err != nil { + return + } + defer closeDir(dir) + + var entry *cephfs2.DirEntryPlus + var ri *provider.ResourceInfo + for entry, err = dir.ReadDirPlus(cephfs2.StatxBasicStats, 0); entry != nil && err == nil; entry, err = dir.ReadDirPlus(cephfs2.StatxBasicStats, 0) { + if fs.conf.HiddenDirs[entry.Name()] { + continue + } + + ri, err = user.fileAsResourceInfo(cv, filepath.Join(path, entry.Name()), entry.Statx(), mdKeys) + if ri == nil || err != nil { + if err != nil { + log := appctx.GetLogger(ctx) + log.Err(err).Msg("cephfs: error in file as resource info") + } + err = nil + continue + } + + files = append(files, ri) + } + }) + + return files, getRevaError(err) +} + +func (fs *cephfs) Download(ctx context.Context, ref *provider.Reference) (rc io.ReadCloser, err error) { + var path string + user := fs.makeUser(ctx) + if path, err = user.resolveRef(ref); err != nil { + return nil, errors.Wrap(err, "cephfs: error resolving ref") + } + + user.op(func(cv *cacheVal) { + if strings.HasPrefix(strings.TrimPrefix(path, user.home), fs.conf.ShareFolder) { + err = errtypes.PermissionDenied("cephfs: cannot download under the virtual share folder") + return + } + rc, err = cv.mount.Open(path, os.O_RDONLY, 0) + }) + + return rc, getRevaError(err) +} + +func (fs *cephfs) ListRevisions(ctx context.Context, ref *provider.Reference) (fvs []*provider.FileVersion, err error) { + //TODO(tmourati): Fix entry id logic + var path string + user := fs.makeUser(ctx) + if path, err = user.resolveRef(ref); err != nil { + return nil, errors.Wrap(err, "cephfs: error resolving ref") + } + + user.op(func(cv *cacheVal) { + if strings.HasPrefix(path, removeLeadingSlash(fs.conf.ShareFolder)) { + err = errtypes.PermissionDenied("cephfs: cannot download under the virtual share folder") + return + } + var dir *cephfs2.Directory + if dir, err = cv.mount.OpenDir(".snap"); err != nil { + return + } + defer closeDir(dir) + + for d, _ := dir.ReadDir(); d != nil; d, _ = dir.ReadDir() { + var revPath string + var stat Statx + var e error + + if strings.HasPrefix(d.Name(), ".") { + continue + } + + revPath, e = resolveRevRef(cv.mount, ref, d.Name()) + if e != nil { + continue + } + stat, e = cv.mount.Statx(revPath, cephfs2.StatxMtime|cephfs2.StatxSize, 0) + if e != nil { + continue + } + fvs = append(fvs, &provider.FileVersion{ + Key: d.Name(), + Size: stat.Size, + Mtime: uint64(stat.Mtime.Sec), + }) + } + }) + + return fvs, getRevaError(err) +} + +func (fs *cephfs) DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (file io.ReadCloser, err error) { + //TODO(tmourati): Fix entry id logic + user := fs.makeUser(ctx) + + user.op(func(cv *cacheVal) { + var revPath string + revPath, err = resolveRevRef(cv.mount, ref, key) + if err != nil { + return + } + + file, err = cv.mount.Open(revPath, os.O_RDONLY, 0) + }) + + return file, getRevaError(err) +} + +func (fs *cephfs) RestoreRevision(ctx context.Context, ref *provider.Reference, key string) (err error) { + //TODO(tmourati): Fix entry id logic + var path string + user := fs.makeUser(ctx) + if path, err = user.resolveRef(ref); err != nil { + return errors.Wrap(err, "cephfs: error resolving ref") + } + + user.op(func(cv *cacheVal) { + var revPath string + if revPath, err = resolveRevRef(cv.mount, ref, key); err != nil { + err = errors.Wrap(err, "cephfs: error resolving revision ref "+ref.String()) + return + } + + var src, dst *cephfs2.File + if src, err = cv.mount.Open(revPath, os.O_RDONLY, 0); err != nil { + return + } + defer closeFile(src) + + if dst, err = cv.mount.Open(path, os.O_WRONLY|os.O_TRUNC, 0); err != nil { + return + } + defer closeFile(dst) + + _, err = io.Copy(dst, src) + }) + + return getRevaError(err) +} + +func (fs *cephfs) GetPathByID(ctx context.Context, id *provider.ResourceId) (str string, err error) { + //TODO(tmourati): Add entry id logic + return "", errtypes.NotSupported("cephfs: entry IDs currently not supported") +} + +func (fs *cephfs) AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) (err error) { + var path string + user := fs.makeUser(ctx) + if path, err = user.resolveRef(ref); err != nil { + return + } + + user.op(func(cv *cacheVal) { + err = fs.changePerms(ctx, cv.mount, g, path, updateGrant) + }) + + return getRevaError(err) +} + +func (fs *cephfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) (err error) { + var path string + user := fs.makeUser(ctx) + if path, err = user.resolveRef(ref); err != nil { + return + } + + user.op(func(cv *cacheVal) { + err = fs.changePerms(ctx, cv.mount, g, path, removeGrant) + }) + + return getRevaError(err) +} + +func (fs *cephfs) UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) (err error) { + var path string + user := fs.makeUser(ctx) + if path, err = user.resolveRef(ref); err != nil { + return + } + + user.op(func(cv *cacheVal) { + err = fs.changePerms(ctx, cv.mount, g, path, updateGrant) + }) + + return getRevaError(err) +} + +func (fs *cephfs) DenyGrant(ctx context.Context, ref *provider.Reference, g *provider.Grantee) (err error) { + var path string + user := fs.makeUser(ctx) + if path, err = user.resolveRef(ref); err != nil { + return + } + + user.op(func(cv *cacheVal) { + grant := &provider.Grant{Grantee: g} //nil perms will remove the whole grant + err = fs.changePerms(ctx, cv.mount, grant, path, removeGrant) + }) + + return getRevaError(err) +} + +func (fs *cephfs) ListGrants(ctx context.Context, ref *provider.Reference) (glist []*provider.Grant, err error) { + var path string + user := fs.makeUser(ctx) + if path, err = user.resolveRef(ref); err != nil { + return + } + + user.op(func(cv *cacheVal) { + glist = fs.getFullPermissionSet(ctx, cv.mount, path) + + if glist == nil { + err = errors.New("cephfs: error listing grants on " + path) + } + }) + + return glist, getRevaError(err) +} + +func (fs *cephfs) GetQuota(ctx context.Context, ref *provider.Reference) (total uint64, used uint64, err error) { + user := fs.makeUser(ctx) + + log := appctx.GetLogger(ctx) + user.op(func(cv *cacheVal) { + var buf []byte + buf, err = cv.mount.GetXattr(".", "ceph.quota.max_bytes") + if err != nil { + log.Warn().Msg("cephfs: user quota bytes not set") + total = fs.conf.UserQuotaBytes + } else { + total, _ = strconv.ParseUint(string(buf), 10, 64) + } + + buf, err = cv.mount.GetXattr(".", "ceph.dir.rbytes") + if err == nil { + used, err = strconv.ParseUint(string(buf), 10, 64) + } + }) + + return total, used, getRevaError(err) +} + +func (fs *cephfs) CreateReference(ctx context.Context, path string, targetURI *url.URL) (err error) { + user := fs.makeUser(ctx) + + user.op(func(cv *cacheVal) { + if !strings.HasPrefix(strings.TrimPrefix(path, user.home), fs.conf.ShareFolder) { + err = errors.New("cephfs: can't create reference outside a share folder") + } else { + err = cv.mount.MakeDir(path, dirPermDefault) + } + }) + if err != nil { + return getRevaError(err) + } + + user.op(func(cv *cacheVal) { + err = cv.mount.SetXattr(path, xattrRef, []byte(targetURI.String()), 0) + }) + + return getRevaError(err) +} + +func (fs *cephfs) Shutdown(ctx context.Context) (err error) { + ctx.Done() + fs.conn.clearCache() + _ = fs.adminConn.adminMount.Unmount() + _ = fs.adminConn.adminMount.Release() + fs.adminConn.radosConn.Shutdown() + + return +} + +func (fs *cephfs) SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) (err error) { + var path string + user := fs.makeUser(ctx) + if path, err = user.resolveRef(ref); err != nil { + return err + } + + user.op(func(cv *cacheVal) { + for k, v := range md.Metadata { + if !strings.HasPrefix(k, xattrUserNs) { + k = xattrUserNs + k + } + if e := cv.mount.SetXattr(path, k, []byte(v), 0); e != nil { + err = errors.Wrap(err, e.Error()) + return + } + } + }) + + return getRevaError(err) +} + +func (fs *cephfs) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) (err error) { + var path string + user := fs.makeUser(ctx) + if path, err = user.resolveRef(ref); err != nil { + return err + } + + user.op(func(cv *cacheVal) { + for _, key := range keys { + if !strings.HasPrefix(key, xattrUserNs) { + key = xattrUserNs + key + } + if e := cv.mount.RemoveXattr(path, key); e != nil { + err = errors.Wrap(err, e.Error()) + return + } + } + }) + + return getRevaError(err) +} + +func (fs *cephfs) EmptyRecycle(ctx context.Context) error { + return errtypes.NotSupported("cephfs: empty recycle not supported") +} + +func (fs *cephfs) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (r *provider.CreateStorageSpaceResponse, err error) { + return nil, errors.New("cephfs: createStorageSpace not supported") +} + +func (fs *cephfs) ListRecycle(ctx context.Context, basePath, key, relativePath string) ([]*provider.RecycleItem, error) { + panic("implement me") +} + +func (fs *cephfs) RestoreRecycleItem(ctx context.Context, basePath, key, relativePath string, restoreRef *provider.Reference) error { + return errors.New("cephfs: restoreRecycleItem not supported") +} + +func (fs *cephfs) PurgeRecycleItem(ctx context.Context, basePath, key, relativePath string) error { + return errors.New("cephfs: purgeRecycleItem not supported") +} + +func (fs *cephfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, permissions map[string]struct{}) ([]*provider.StorageSpace, error) { + return nil, errors.New("cephfs: listStorageSpaces not supported") +} + +func (fs *cephfs) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) { + return nil, errors.New("cephfs: updateStorageSpace not supported") +} diff --git a/pkg/storage/fs/cephfs/chunking.go b/pkg/storage/fs/cephfs/chunking.go new file mode 100644 index 00000000000..bb4e48fe2c7 --- /dev/null +++ b/pkg/storage/fs/cephfs/chunking.go @@ -0,0 +1,344 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +//go:build ceph +// +build ceph + +package cephfs + +import ( + "context" + "fmt" + "io" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" + "time" + + cephfs2 "github.com/ceph/go-ceph/cephfs" + "github.com/google/uuid" +) + +// IsChunked checks if a given path refers to a chunk or not +func IsChunked(fn string) (bool, error) { + // FIXME: also need to check whether the OC-Chunked header is set + return regexp.MatchString(`-chunking-\w+-[0-9]+-[0-9]+$`, fn) +} + +// ChunkBLOBInfo stores info about a particular chunk +type ChunkBLOBInfo struct { + Path string + TransferID string + TotalChunks int + CurrentChunk int +} + +// Not using the resource path in the chunk folder name allows uploading to +// the same folder after a move without having to restart the chunk upload +func (c *ChunkBLOBInfo) uploadID() string { + return fmt.Sprintf("chunking-%s-%d", c.TransferID, c.TotalChunks) +} + +// GetChunkBLOBInfo decodes a chunk name to retrieve info about it. +func GetChunkBLOBInfo(path string) (*ChunkBLOBInfo, error) { + parts := strings.Split(path, "-chunking-") + tail := strings.Split(parts[1], "-") + + totalChunks, err := strconv.Atoi(tail[1]) + if err != nil { + return nil, err + } + + currentChunk, err := strconv.Atoi(tail[2]) + if err != nil { + return nil, err + } + if currentChunk >= totalChunks { + return nil, fmt.Errorf("current chunk:%d exceeds total number of chunks:%d", currentChunk, totalChunks) + } + + return &ChunkBLOBInfo{ + Path: parts[0], + TransferID: tail[0], + TotalChunks: totalChunks, + CurrentChunk: currentChunk, + }, nil +} + +// ChunkHandler manages chunked uploads, storing the chunks in a temporary directory +// until it gets the final chunk which is then returned. +type ChunkHandler struct { + user *User + chunkFolder string +} + +// NewChunkHandler creates a handler for chunked uploads. +func NewChunkHandler(ctx context.Context, fs *cephfs) *ChunkHandler { + return &ChunkHandler{fs.makeUser(ctx), fs.conf.UploadFolder} +} + +func (c *ChunkHandler) getChunkTempFileName() string { + return fmt.Sprintf("__%d_%s", time.Now().Unix(), uuid.New().String()) +} + +func (c *ChunkHandler) getChunkFolderName(i *ChunkBLOBInfo) (path string, err error) { + path = filepath.Join(c.chunkFolder, i.uploadID()) + c.user.op(func(cv *cacheVal) { + err = cv.mount.MakeDir(path, 0777) + }) + + return +} + +func (c *ChunkHandler) saveChunk(path string, r io.ReadCloser) (finish bool, chunk string, err error) { + var chunkInfo *ChunkBLOBInfo + + chunkInfo, err = GetChunkBLOBInfo(path) + if err != nil { + err = fmt.Errorf("error getting chunk info from path: %s", path) + return + } + + chunkTempFilename := c.getChunkTempFileName() + c.user.op(func(cv *cacheVal) { + var tmpFile *cephfs2.File + target := filepath.Join(c.chunkFolder, chunkTempFilename) + tmpFile, err = cv.mount.Open(target, os.O_CREATE|os.O_WRONLY, filePermDefault) + defer closeFile(tmpFile) + if err != nil { + return + } + _, err = io.Copy(tmpFile, r) + }) + if err != nil { + return + } + + chunksFolderName, err := c.getChunkFolderName(chunkInfo) + if err != nil { + return + } + // c.logger.Info().Log("chunkfolder", chunksFolderName) + + chunkTarget := filepath.Join(chunksFolderName, strconv.Itoa(chunkInfo.CurrentChunk)) + c.user.op(func(cv *cacheVal) { + err = cv.mount.Rename(chunkTempFilename, chunkTarget) + }) + if err != nil { + return + } + + // Check that all chunks are uploaded. + // This is very inefficient, the server has to check that it has all the + // chunks after each uploaded chunk. + // A two-phase upload like DropBox is better, because the server will + // assembly the chunks when the client asks for it. + numEntries := 0 + c.user.op(func(cv *cacheVal) { + var dir *cephfs2.Directory + var entry *cephfs2.DirEntry + var chunkFile, assembledFile *cephfs2.File + + dir, err = cv.mount.OpenDir(chunksFolderName) + defer closeDir(dir) + + for entry, err = dir.ReadDir(); entry != nil && err == nil; entry, err = dir.ReadDir() { + numEntries++ + } + // to remove . and .. + numEntries -= 2 + + if err != nil || numEntries < chunkInfo.TotalChunks { + return + } + + chunk = filepath.Join(c.chunkFolder, c.getChunkTempFileName()) + assembledFile, err = cv.mount.Open(chunk, os.O_CREATE|os.O_WRONLY, filePermDefault) + defer closeFile(assembledFile) + defer deleteFile(cv.mount, chunk) + if err != nil { + return + } + + for i := 0; i < numEntries; i++ { + target := filepath.Join(chunksFolderName, strconv.Itoa(i)) + + chunkFile, err = cv.mount.Open(target, os.O_RDONLY, 0) + if err != nil { + return + } + _, err = io.Copy(assembledFile, chunkFile) + closeFile(chunkFile) + if err != nil { + return + } + } + + // necessary approach in case assembly fails + for i := 0; i < numEntries; i++ { + target := filepath.Join(chunksFolderName, strconv.Itoa(i)) + err = cv.mount.Unlink(target) + if err != nil { + return + } + } + _ = cv.mount.Unlink(chunksFolderName) + }) + + return true, chunk, nil +} + +// WriteChunk saves an intermediate chunk temporarily and assembles all chunks +// once the final one is received. +func (c *ChunkHandler) WriteChunk(fn string, r io.ReadCloser) (string, string, error) { + finish, chunk, err := c.saveChunk(fn, r) + if err != nil { + return "", "", err + } + + if !finish { + return "", "", nil + } + + chunkInfo, err := GetChunkBLOBInfo(fn) + if err != nil { + return "", "", err + } + + return chunkInfo.Path, chunk, nil + + // TODO(labkode): implement old chunking + + /* + req2 := &provider.StartWriteSessionRequest{} + res2, err := client.StartWriteSession(ctx, req2) + if err != nil { + logger.Error(ctx, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + if res2.Status.Code != rpc.Code_CODE_OK { + logger.Println(ctx, res2.Status) + w.WriteHeader(http.StatusInternalServerError) + return + } + + sessID := res2.SessionId + logger.Build().Str("sessID", sessID).Msg(ctx, "got write session id") + + stream, err := client.Write(ctx) + if err != nil { + logger.Error(ctx, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + buffer := make([]byte, 1024*1024*3) + var offset uint64 + var numChunks uint64 + + for { + n, err := fd.Read(buffer) + if n > 0 { + req := &provider.WriteRequest{Data: buffer, Length: uint64(n), SessionId: sessID, Offset: offset} + err = stream.Send(req) + if err != nil { + logger.Error(ctx, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + numChunks++ + offset += uint64(n) + } + + if err == io.EOF { + break + } + + if err != nil { + logger.Error(ctx, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + } + + res3, err := stream.CloseAndRecv() + if err != nil { + logger.Error(ctx, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + if res3.Status.Code != rpc.Code_CODE_OK { + logger.Println(ctx, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + req4 := &provider.FinishWriteSessionRequest{Filename: chunkInfo.path, SessionId: sessID} + res4, err := client.FinishWriteSession(ctx, req4) + if err != nil { + logger.Error(ctx, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + if res4.Status.Code != rpc.Code_CODE_OK { + logger.Println(ctx, res4.Status) + w.WriteHeader(http.StatusInternalServerError) + return + } + + req.Filename = chunkInfo.path + res, err = client.Stat(ctx, req) + if err != nil { + logger.Error(ctx, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + if res.Status.Code != rpc.Code_CODE_OK { + logger.Println(ctx, res.Status) + w.WriteHeader(http.StatusInternalServerError) + return + } + + md2 := res.Metadata + + w.Header().Add("Content-Type", md2.Mime) + w.Header().Set("ETag", md2.Etag) + w.Header().Set("OC-FileId", md2.Id) + w.Header().Set("OC-ETag", md2.Etag) + t := time.Unix(int64(md2.Mtime), 0) + lastModifiedString := t.Format(time.RFC1123Z) + w.Header().Set("Last-Modified", lastModifiedString) + w.Header().Set("X-OC-MTime", "accepted") + + if md == nil { + w.WriteHeader(http.StatusCreated) + return + } + + w.WriteHeader(http.StatusNoContent) + return + */ +} diff --git a/pkg/storage/fs/cephfs/connections.go b/pkg/storage/fs/cephfs/connections.go new file mode 100644 index 00000000000..7b928eaa633 --- /dev/null +++ b/pkg/storage/fs/cephfs/connections.go @@ -0,0 +1,315 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +//go:build ceph +// +build ceph + +package cephfs + +import ( + "context" + "fmt" + "time" + + "github.com/ceph/go-ceph/cephfs/admin" + rados2 "github.com/ceph/go-ceph/rados" + grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/pkg/errors" + + cephfs2 "github.com/ceph/go-ceph/cephfs" + "github.com/dgraph-io/ristretto" + "golang.org/x/sync/semaphore" +) + +type cacheVal struct { + perm *cephfs2.UserPerm + mount *cephfs2.MountInfo +} + +//TODO: Add to cephfs obj + +type connections struct { + cache *ristretto.Cache + lock *semaphore.Weighted + ctx context.Context + userCache *ristretto.Cache + groupCache *ristretto.Cache +} + +//TODO: make configurable/add to options +var usrLimit int64 = 1e4 + +func newCache() (c *connections, err error) { + cache, err := ristretto.NewCache(&ristretto.Config{ + NumCounters: 1e7, + MaxCost: usrLimit, + BufferItems: 64, + OnEvict: func(item *ristretto.Item) { + v := item.Value.(cacheVal) + v.perm.Destroy() + _ = v.mount.Unmount() + _ = v.mount.Release() + }, + }) + if err != nil { + return + } + + ucache, err := ristretto.NewCache(&ristretto.Config{ + NumCounters: 1e7, + MaxCost: 10 * usrLimit, + BufferItems: 64, + }) + if err != nil { + return + } + + gcache, err := ristretto.NewCache(&ristretto.Config{ + NumCounters: 1e7, + MaxCost: 10 * usrLimit, + BufferItems: 64, + }) + if err != nil { + return + } + + c = &connections{ + cache: cache, + lock: semaphore.NewWeighted(usrLimit), + ctx: context.Background(), + userCache: ucache, + groupCache: gcache, + } + + return +} + +func (c *connections) clearCache() { + c.cache.Clear() + c.cache.Close() +} + +type adminConn struct { + indexPoolName string + subvolAdmin *admin.FSAdmin + adminMount Mount + radosConn *rados2.Conn + radosIO *rados2.IOContext +} + +func newAdminConn(poolName string) *adminConn { + rados, err := rados2.NewConn() + if err != nil { + return nil + } + if err = rados.ReadDefaultConfigFile(); err != nil { + return nil + } + + if err = rados.Connect(); err != nil { + return nil + } + + pools, err := rados.ListPools() + if err != nil { + rados.Shutdown() + return nil + } + + var radosIO *rados2.IOContext + if in(poolName, pools) { + radosIO, err = rados.OpenIOContext(poolName) + if err != nil { + rados.Shutdown() + return nil + } + } else { + err = rados.MakePool(poolName) + if err != nil { + rados.Shutdown() + return nil + } + radosIO, err = rados.OpenIOContext(poolName) + if err != nil { + rados.Shutdown() + return nil + } + } + + mount, err := cephfs2.CreateFromRados(rados) + if err != nil { + rados.Shutdown() + return nil + } + + if err = mount.Mount(); err != nil { + rados.Shutdown() + destroyCephConn(mount, nil) + return nil + } + + return &adminConn{ + poolName, + admin.NewFromConn(rados), + mount, + rados, + radosIO, + } +} + +func newConn(user *User) *cacheVal { + var perm *cephfs2.UserPerm + mount, err := cephfs2.CreateMount() + if err != nil { + return destroyCephConn(mount, perm) + } + if err = mount.ReadDefaultConfigFile(); err != nil { + return destroyCephConn(mount, perm) + } + if err = mount.Init(); err != nil { + return destroyCephConn(mount, perm) + } + + if user != nil { //nil creates admin conn + perm = cephfs2.NewUserPerm(int(user.UidNumber), int(user.GidNumber), []int{}) + if err = mount.SetMountPerms(perm); err != nil { + return destroyCephConn(mount, perm) + } + } + + if err = mount.MountWithRoot("/"); err != nil { + return destroyCephConn(mount, perm) + } + + if user != nil { + if err = mount.ChangeDir(user.fs.conf.Root); err != nil { + return destroyCephConn(mount, perm) + } + } + + return &cacheVal{ + perm: perm, + mount: mount, + } +} + +func (fs *cephfs) getUserByID(ctx context.Context, uid string) (*userpb.User, error) { + if entity, found := fs.conn.userCache.Get(uid); found { + return entity.(*userpb.User), nil + } + + client, err := pool.GetGatewayServiceClient(fs.conf.GatewaySvc) + if err != nil { + return nil, errors.Wrap(err, "cephfs: error getting gateway grpc client") + } + getUserResp, err := client.GetUserByClaim(ctx, &userpb.GetUserByClaimRequest{ + Claim: "uid", + Value: uid, + }) + + if err != nil { + return nil, errors.Wrap(err, "cephfs: error getting user") + } + if getUserResp.Status.Code != rpc.Code_CODE_OK { + return nil, errors.Wrap(err, "cephfs: grpc get user failed") + } + fs.conn.userCache.SetWithTTL(uid, getUserResp.User, 1, 24*time.Hour) + fs.conn.userCache.SetWithTTL(getUserResp.User.Id.OpaqueId, getUserResp.User, 1, 24*time.Hour) + + return getUserResp.User, nil +} + +func (fs *cephfs) getUserByOpaqueID(ctx context.Context, oid string) (*userpb.User, error) { + if entity, found := fs.conn.userCache.Get(oid); found { + return entity.(*userpb.User), nil + } + client, err := pool.GetGatewayServiceClient(fs.conf.GatewaySvc) + if err != nil { + return nil, errors.Wrap(err, "cephfs: error getting gateway grpc client") + } + getUserResp, err := client.GetUser(ctx, &userpb.GetUserRequest{ + UserId: &userpb.UserId{ + OpaqueId: oid, + }, + }) + + if err != nil { + return nil, errors.Wrap(err, "cephfs: error getting user") + } + if getUserResp.Status.Code != rpc.Code_CODE_OK { + return nil, errors.Wrap(err, "cephfs: grpc get user failed") + } + fs.conn.userCache.SetWithTTL(fmt.Sprint(getUserResp.User.UidNumber), getUserResp.User, 1, 24*time.Hour) + fs.conn.userCache.SetWithTTL(oid, getUserResp.User, 1, 24*time.Hour) + + return getUserResp.User, nil +} + +func (fs *cephfs) getGroupByID(ctx context.Context, gid string) (*grouppb.Group, error) { + if entity, found := fs.conn.groupCache.Get(gid); found { + return entity.(*grouppb.Group), nil + } + + client, err := pool.GetGatewayServiceClient(fs.conf.GatewaySvc) + if err != nil { + return nil, errors.Wrap(err, "cephfs: error getting gateway grpc client") + } + getGroupResp, err := client.GetGroupByClaim(ctx, &grouppb.GetGroupByClaimRequest{ + Claim: "gid", + Value: gid, + }) + if err != nil { + return nil, errors.Wrap(err, "cephfs: error getting group") + } + if getGroupResp.Status.Code != rpc.Code_CODE_OK { + return nil, errors.Wrap(err, "cephfs: grpc get group failed") + } + fs.conn.groupCache.SetWithTTL(gid, getGroupResp.Group, 1, 24*time.Hour) + fs.conn.groupCache.SetWithTTL(getGroupResp.Group.Id.OpaqueId, getGroupResp.Group, 1, 24*time.Hour) + + return getGroupResp.Group, nil +} + +func (fs *cephfs) getGroupByOpaqueID(ctx context.Context, oid string) (*grouppb.Group, error) { + if entity, found := fs.conn.groupCache.Get(oid); found { + return entity.(*grouppb.Group), nil + } + client, err := pool.GetGatewayServiceClient(fs.conf.GatewaySvc) + if err != nil { + return nil, errors.Wrap(err, "cephfs: error getting gateway grpc client") + } + getGroupResp, err := client.GetGroup(ctx, &grouppb.GetGroupRequest{ + GroupId: &grouppb.GroupId{ + OpaqueId: oid, + }, + }) + + if err != nil { + return nil, errors.Wrap(err, "cephfs: error getting group") + } + if getGroupResp.Status.Code != rpc.Code_CODE_OK { + return nil, errors.Wrap(err, "cephfs: grpc get group failed") + } + fs.conn.userCache.SetWithTTL(fmt.Sprint(getGroupResp.Group.GidNumber), getGroupResp.Group, 1, 24*time.Hour) + fs.conn.userCache.SetWithTTL(oid, getGroupResp.Group, 1, 24*time.Hour) + + return getGroupResp.Group, nil +} diff --git a/pkg/storage/fs/cephfs/errors.go b/pkg/storage/fs/cephfs/errors.go new file mode 100644 index 00000000000..a4ab013c977 --- /dev/null +++ b/pkg/storage/fs/cephfs/errors.go @@ -0,0 +1,64 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +//go:build ceph +// +build ceph + +package cephfs + +/* + #include + #include + #include +*/ +import "C" +import ( + "fmt" + + "github.com/cs3org/reva/pkg/errtypes" +) + +func wrapErrorMsg(code C.int) string { + return fmt.Sprintf("cephfs: ret=-%d, %s", code, C.GoString(C.strerror(code))) +} + +var ( + errNotFound = wrapErrorMsg(C.ENOENT) + errFileExists = wrapErrorMsg(C.EEXIST) + errNoSpaceLeft = wrapErrorMsg(C.ENOSPC) + errIsADirectory = wrapErrorMsg(C.EISDIR) + errPermissionDenied = wrapErrorMsg(C.EACCES) +) + +func getRevaError(err error) error { + if err == nil { + return nil + } + switch err.Error() { + case errNotFound: + return errtypes.NotFound("cephfs: dir entry not found") + case errPermissionDenied: + return errtypes.PermissionDenied("cephfs: permission denied") + case errFileExists: + return errtypes.AlreadyExists("cephfs: file already exists") + case errNoSpaceLeft: + return errtypes.InsufficientStorage("cephfs: no space left on device") + default: + return errtypes.InternalError(err.Error()) + } +} diff --git a/pkg/storage/fs/cephfs/options.go b/pkg/storage/fs/cephfs/options.go new file mode 100644 index 00000000000..0b4b81f0e69 --- /dev/null +++ b/pkg/storage/fs/cephfs/options.go @@ -0,0 +1,90 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +//go:build ceph +// +build ceph + +package cephfs + +import ( + "path/filepath" + + "github.com/cs3org/reva/pkg/sharedconf" +) + +// Options for the cephfs module +type Options struct { + GatewaySvc string `mapstructure:"gatewaysvc"` + IndexPool string `mapstructure:"index_pool"` + Root string `mapstructure:"root"` + ShadowFolder string `mapstructure:"shadow_folder"` + ShareFolder string `mapstructure:"share_folder"` + UploadFolder string `mapstructure:"uploads"` + UserLayout string `mapstructure:"user_layout"` + + DisableHome bool `mapstructure:"disable_home"` + UserQuotaBytes uint64 `mapstructure:"user_quota_bytes"` + HiddenDirs map[string]bool +} + +func (c *Options) fillDefaults() { + c.GatewaySvc = sharedconf.GetGatewaySVC(c.GatewaySvc) + + if c.IndexPool == "" { + c.IndexPool = "path_index" + } + + if c.Root == "" { + c.Root = "/home" + } else { + c.Root = addLeadingSlash(c.Root) //force absolute path in case leading "/" is omitted + } + + if c.ShadowFolder == "" { + c.ShadowFolder = "/.reva_hidden" + } else { + c.ShadowFolder = addLeadingSlash(c.ShadowFolder) + } + + if c.ShareFolder == "" { + c.ShareFolder = "/Shares" + } else { + c.ShareFolder = addLeadingSlash(c.ShareFolder) + } + + if c.UploadFolder == "" { + c.UploadFolder = ".uploads" + } + c.UploadFolder = filepath.Join(c.ShadowFolder, c.UploadFolder) + + if c.UserLayout == "" { + c.UserLayout = "{{.Username}}" + } + + c.HiddenDirs = map[string]bool{ + ".": true, + "..": true, + removeLeadingSlash(c.ShadowFolder): true, + } + + c.DisableHome = false // it is currently only home based + + if c.UserQuotaBytes == 0 { + c.UserQuotaBytes = 50000000000 + } +} diff --git a/pkg/storage/fs/cephfs/permissions.go b/pkg/storage/fs/cephfs/permissions.go new file mode 100644 index 00000000000..8b4a32484de --- /dev/null +++ b/pkg/storage/fs/cephfs/permissions.go @@ -0,0 +1,325 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +//go:build ceph +// +build ceph + +package cephfs + +import ( + "context" + "errors" + "fmt" + "strings" + + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + + cephfs2 "github.com/ceph/go-ceph/cephfs" + grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/maxymania/go-system/posix_acl" +) + +var perms = map[rune][]string{ + 'r': { + "Stat", + "GetPath", + "GetQuota", + "InitiateFileDownload", + "ListGrants", + }, + 'w': { + "AddGrant", + "CreateContainer", + "Delete", + "InitiateFileUpload", + "Move", + "RemoveGrant", + "PurgeRecycle", + "RestoreFileVersion", + "RestoreRecycleItem", + "UpdateGrant", + }, + 'x': { + "ListRecycle", + "ListContainer", + "ListFileVersions", + }, +} + +const ( + aclXattr = "system.posix_acl_access" +) + +var op2int = map[rune]uint16{'r': 4, 'w': 2, 'x': 1} + +func getPermissionSet(user *User, stat *cephfs2.CephStatx, mount Mount, path string) (perm *provider.ResourcePermissions) { + perm = &provider.ResourcePermissions{} + + if int64(stat.Uid) == user.UidNumber || int64(stat.Gid) == user.GidNumber { + updatePerms(perm, "rwx", false) + return + } + + acls := &posix_acl.Acl{} + var xattr []byte + var err error + if xattr, err = mount.GetXattr(path, aclXattr); err != nil { + return nil + } + acls.Decode(xattr) + + group, err := user.fs.getGroupByID(user.ctx, fmt.Sprint(stat.Gid)) + + for _, acl := range acls.List { + rwx := strings.Split(acl.String(), ":")[2] + switch acl.GetType() { + case posix_acl.ACL_USER: + if int64(acl.GetID()) == user.UidNumber { + updatePerms(perm, rwx, false) + } + case posix_acl.ACL_GROUP: + if int64(acl.GetID()) == user.GidNumber || in(group.GroupName, user.Groups) { + updatePerms(perm, rwx, false) + } + case posix_acl.ACL_MASK: + updatePerms(perm, rwx, true) + case posix_acl.ACL_OTHERS: + updatePerms(perm, rwx, false) + } + } + + return +} + +func (fs *cephfs) getFullPermissionSet(ctx context.Context, mount Mount, path string) (permList []*provider.Grant) { + acls := &posix_acl.Acl{} + var xattr []byte + var err error + if xattr, err = mount.GetXattr(path, aclXattr); err != nil { + return nil + } + acls.Decode(xattr) + + for _, acl := range acls.List { + rwx := strings.Split(acl.String(), ":")[2] + switch acl.GetType() { + case posix_acl.ACL_USER: + user, err := fs.getUserByID(ctx, fmt.Sprint(acl.GetID())) + if err != nil { + return nil + } + userGrant := &provider.Grant{ + Grantee: &provider.Grantee{ + Type: provider.GranteeType_GRANTEE_TYPE_USER, + Id: &provider.Grantee_UserId{UserId: user.Id}, + }, + Permissions: &provider.ResourcePermissions{}, + } + updatePerms(userGrant.Permissions, rwx, false) + permList = append(permList, userGrant) + case posix_acl.ACL_GROUP: + group, err := fs.getGroupByID(ctx, fmt.Sprint(acl.GetID())) + if err != nil { + return nil + } + groupGrant := &provider.Grant{ + Grantee: &provider.Grantee{ + Type: provider.GranteeType_GRANTEE_TYPE_GROUP, + Id: &provider.Grantee_GroupId{GroupId: group.Id}, + }, + Permissions: &provider.ResourcePermissions{}, + } + updatePerms(groupGrant.Permissions, rwx, false) + permList = append(permList, groupGrant) + } + } + + return +} + +/* +func permToIntRefl(p *provider.ResourcePermissions) (result uint16) { + if p == nil { return 0b111 } //rwx + + item := reflect.ValueOf(p).Elem() + for _, op := range "rwx" { + for _, perm := range perms[op] { + if item.FieldByName(perm).Bool() { + result |= op2int[op] + break //if value is 1 then bitwise OR can never change it again + } + } + } + + return +} +*/ + +func permToInt(rp *provider.ResourcePermissions) (result uint16) { + if rp == nil { + return 0b111 // rwx + } + if rp.Stat || rp.GetPath || rp.GetQuota || rp.ListGrants || rp.InitiateFileDownload { + result |= 4 + } + if rp.CreateContainer || rp.Move || rp.Delete || rp.InitiateFileUpload || rp.AddGrant || rp.UpdateGrant || + rp.RemoveGrant || rp.DenyGrant || rp.RestoreFileVersion || rp.PurgeRecycle || rp.RestoreRecycleItem { + result |= 2 + } + if rp.ListRecycle || rp.ListContainer || rp.ListFileVersions { + result |= 1 + } + + return +} + +const ( + updateGrant = iota + removeGrant = iota +) + +func (fs *cephfs) changePerms(ctx context.Context, mt Mount, grant *provider.Grant, path string, method int) (err error) { + buf, err := mt.GetXattr(path, aclXattr) + if err != nil { + return + } + acls := &posix_acl.Acl{} + acls.Decode(buf) + var sid posix_acl.AclSID + + switch grant.Grantee.Type { + case provider.GranteeType_GRANTEE_TYPE_USER: + var user *userpb.User + if user, err = fs.getUserByOpaqueID(ctx, grant.Grantee.GetUserId().OpaqueId); err != nil { + return + } + sid.SetUid(uint32(user.UidNumber)) + case provider.GranteeType_GRANTEE_TYPE_GROUP: + var group *grouppb.Group + if group, err = fs.getGroupByOpaqueID(ctx, grant.Grantee.GetGroupId().OpaqueId); err != nil { + return + } + sid.SetGid(uint32(group.GidNumber)) + default: + return errors.New("cephfs: invalid grantee type") + } + + var found = false + var i int + for i = range acls.List { + if acls.List[i].AclSID == sid { + found = true + } + } + + if method == updateGrant { + if found { + acls.List[i].Perm |= permToInt(grant.Permissions) + if acls.List[i].Perm == 0 { // remove empty grant + acls.List = append(acls.List[:i], acls.List[i+1:]...) + } + } else { + acls.List = append(acls.List, posix_acl.AclElement{ + AclSID: sid, + Perm: permToInt(grant.Permissions), + }) + } + } else { //removeGrant + if found { + acls.List[i].Perm &^= permToInt(grant.Permissions) //bitwise and-not, to clear bits on Perm + if acls.List[i].Perm == 0 { // remove empty grant + acls.List = append(acls.List[:i], acls.List[i+1:]...) + } + } + } + + err = mt.SetXattr(path, aclXattr, acls.Encode(), 0) + + return +} + +/* +func updatePermsRefl(rp *provider.ResourcePermissions, acl string, unset bool) { + if rp == nil { return } + for _, t := range "rwx" { + if strings.ContainsRune(acl, t) { + for _, i := range perms[t] { + reflect.ValueOf(rp).Elem().FieldByName(i).SetBool(true) + } + } else if unset { + for _, i := range perms[t] { + reflect.ValueOf(rp).Elem().FieldByName(i).SetBool(false) + } + } + } +} +*/ + +func updatePerms(rp *provider.ResourcePermissions, acl string, unset bool) { + if rp == nil { + return + } + if strings.ContainsRune(acl, 'r') { + rp.Stat = true + rp.GetPath = true + rp.GetQuota = true + rp.InitiateFileDownload = true + rp.ListGrants = true + } else if unset { + rp.Stat = false + rp.GetPath = false + rp.GetQuota = false + rp.InitiateFileDownload = false + rp.ListGrants = false + } + if strings.ContainsRune(acl, 'w') { + rp.AddGrant = true + rp.DenyGrant = true + rp.CreateContainer = true + rp.Delete = true + rp.InitiateFileUpload = true + rp.Move = true + rp.RemoveGrant = true + rp.PurgeRecycle = true + rp.RestoreFileVersion = true + rp.RestoreRecycleItem = true + rp.UpdateGrant = true + } else if unset { + rp.AddGrant = false + rp.DenyGrant = false + rp.CreateContainer = false + rp.Delete = false + rp.InitiateFileUpload = false + rp.Move = false + rp.RemoveGrant = false + rp.PurgeRecycle = false + rp.RestoreFileVersion = false + rp.RestoreRecycleItem = false + rp.UpdateGrant = false + } + if strings.ContainsRune(acl, 'x') { + rp.ListRecycle = true + rp.ListContainer = true + rp.ListFileVersions = true + } else if unset { + rp.ListRecycle = false + rp.ListContainer = false + rp.ListFileVersions = false + } +} diff --git a/pkg/storage/fs/cephfs/unsupported.go b/pkg/storage/fs/cephfs/unsupported.go new file mode 100644 index 00000000000..a337f3f7895 --- /dev/null +++ b/pkg/storage/fs/cephfs/unsupported.go @@ -0,0 +1,39 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +//go:build !ceph +// +build !ceph + +package cephfs + +import ( + "github.com/pkg/errors" + + "github.com/cs3org/reva/pkg/storage" + "github.com/cs3org/reva/pkg/storage/fs/registry" +) + +func init() { + registry.Register("cephfs", New) +} + +// New returns an implementation to of the storage.FS interface that talk to +// a ceph filesystem. +func New(m map[string]interface{}) (storage.FS, error) { + return nil, errors.New("cephfs: revad was compiled without CephFS support") +} diff --git a/pkg/storage/fs/cephfs/upload.go b/pkg/storage/fs/cephfs/upload.go new file mode 100644 index 00000000000..2d8eccf7c97 --- /dev/null +++ b/pkg/storage/fs/cephfs/upload.go @@ -0,0 +1,413 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +//go:build ceph +// +build ceph + +package cephfs + +import ( + "bytes" + "context" + "encoding/json" + "io" + "os" + "path/filepath" + + cephfs2 "github.com/ceph/go-ceph/cephfs" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/pkg/appctx" + ctx2 "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/utils" + "github.com/google/uuid" + "github.com/pkg/errors" + tusd "github.com/tus/tusd/pkg/handler" +) + +func (fs *cephfs) Upload(ctx context.Context, ref *provider.Reference, r io.ReadCloser) error { + user := fs.makeUser(ctx) + upload, err := fs.GetUpload(ctx, ref.GetPath()) + if err != nil { + metadata := map[string]string{"sizedeferred": "true"} + uploadIDs, err := fs.InitiateUpload(ctx, ref, 0, metadata) + if err != nil { + return err + } + if upload, err = fs.GetUpload(ctx, uploadIDs["simple"]); err != nil { + return errors.Wrap(err, "cephfs: error retrieving upload") + } + } + + uploadInfo := upload.(*fileUpload) + + p := uploadInfo.info.Storage["InternalDestination"] + ok, err := IsChunked(p) + if err != nil { + return errors.Wrap(err, "cephfs: error checking path") + } + if ok { + var assembledFile string + p, assembledFile, err = NewChunkHandler(ctx, fs).WriteChunk(p, r) + if err != nil { + return err + } + if p == "" { + if err = uploadInfo.Terminate(ctx); err != nil { + return errors.Wrap(err, "cephfs: error removing auxiliary files") + } + return errtypes.PartialContent(ref.String()) + } + uploadInfo.info.Storage["InternalDestination"] = p + + user.op(func(cv *cacheVal) { + r, err = cv.mount.Open(assembledFile, os.O_RDONLY, 0) + }) + if err != nil { + return errors.Wrap(err, "cephfs: error opening assembled file") + } + defer r.Close() + defer user.op(func(cv *cacheVal) { + _ = cv.mount.Unlink(assembledFile) + }) + } + + if _, err := uploadInfo.WriteChunk(ctx, 0, r); err != nil { + return errors.Wrap(err, "cephfs: error writing to binary file") + } + + return uploadInfo.FinishUpload(ctx) +} + +func (fs *cephfs) InitiateUpload(ctx context.Context, ref *provider.Reference, uploadLength int64, metadata map[string]string) (map[string]string, error) { + user := fs.makeUser(ctx) + np, err := user.resolveRef(ref) + if err != nil { + return nil, errors.Wrap(err, "cephfs: error resolving reference") + } + + info := tusd.FileInfo{ + MetaData: tusd.MetaData{ + "filename": filepath.Base(np), + "dir": filepath.Dir(np), + }, + Size: uploadLength, + } + + if metadata != nil { + if metadata["mtime"] != "" { + info.MetaData["mtime"] = metadata["mtime"] + } + if _, ok := metadata["sizedeferred"]; ok { + info.SizeIsDeferred = true + } + } + + upload, err := fs.NewUpload(ctx, info) + if err != nil { + return nil, err + } + + info, _ = upload.GetInfo(ctx) + + return map[string]string{ + "simple": info.ID, + "tus": info.ID, + }, nil +} + +// UseIn tells the tus upload middleware which extensions it supports. +func (fs *cephfs) UseIn(composer *tusd.StoreComposer) { + composer.UseCore(fs) + composer.UseTerminater(fs) +} + +func (fs *cephfs) NewUpload(ctx context.Context, info tusd.FileInfo) (upload tusd.Upload, err error) { + log := appctx.GetLogger(ctx) + log.Debug().Interface("info", info).Msg("cephfs: NewUpload") + + user := fs.makeUser(ctx) + + fn := info.MetaData["filename"] + if fn == "" { + return nil, errors.New("cephfs: missing filename in metadata") + } + info.MetaData["filename"] = filepath.Clean(info.MetaData["filename"]) + + dir := info.MetaData["dir"] + if dir == "" { + return nil, errors.New("cephfs: missing dir in metadata") + } + info.MetaData["dir"] = filepath.Clean(info.MetaData["dir"]) + + np := filepath.Join(info.MetaData["dir"], info.MetaData["filename"]) + + info.ID = uuid.New().String() + + binPath := fs.getUploadPath(info.ID) + + info.Storage = map[string]string{ + "Type": "Cephfs", + "BinPath": binPath, + "InternalDestination": np, + + "Idp": user.Id.Idp, + "UserId": user.Id.OpaqueId, + "UserName": user.Username, + "UserType": utils.UserTypeToString(user.Id.Type), + + "LogLevel": log.GetLevel().String(), + } + + // Create binary file with no content + user.op(func(cv *cacheVal) { + var f *cephfs2.File + defer closeFile(f) + f, err = cv.mount.Open(binPath, os.O_CREATE|os.O_WRONLY, filePermDefault) + if err != nil { + return + } + }) + //TODO: if we get two same upload ids, the second one can't upload at all + if err != nil { + return + } + + upload = &fileUpload{ + info: info, + binPath: binPath, + infoPath: binPath + ".info", + fs: fs, + ctx: ctx, + } + + if !info.SizeIsDeferred && info.Size == 0 { + log.Debug().Interface("info", info).Msg("cephfs: finishing upload for empty file") + // no need to create info file and finish directly + err = upload.FinishUpload(ctx) + + return + } + + // writeInfo creates the file by itself if necessary + err = upload.(*fileUpload).writeInfo() + + return +} + +func (fs *cephfs) getUploadPath(uploadID string) string { + return filepath.Join(fs.conf.UploadFolder, uploadID) +} + +// GetUpload returns the Upload for the given upload id +func (fs *cephfs) GetUpload(ctx context.Context, id string) (fup tusd.Upload, err error) { + binPath := fs.getUploadPath(id) + info := tusd.FileInfo{} + if err != nil { + return nil, errtypes.NotFound("bin path for upload " + id + " not found") + } + infoPath := binPath + ".info" + + var data bytes.Buffer + f, err := fs.adminConn.adminMount.Open(infoPath, os.O_RDONLY, 0) + if err != nil { + return + } + _, err = io.Copy(&data, f) + if err != nil { + return + } + if err = json.Unmarshal(data.Bytes(), &info); err != nil { + return + } + + u := &userpb.User{ + Id: &userpb.UserId{ + Idp: info.Storage["Idp"], + OpaqueId: info.Storage["UserId"], + }, + Username: info.Storage["UserName"], + } + ctx = ctx2.ContextSetUser(ctx, u) + user := fs.makeUser(ctx) + + var stat Statx + user.op(func(cv *cacheVal) { + stat, err = cv.mount.Statx(binPath, cephfs2.StatxSize, 0) + }) + if err != nil { + return + } + info.Offset = int64(stat.Size) + + return &fileUpload{ + info: info, + binPath: binPath, + infoPath: infoPath, + fs: fs, + ctx: ctx, + }, nil +} + +type fileUpload struct { + // info stores the current information about the upload + info tusd.FileInfo + // infoPath is the path to the .info file + infoPath string + // binPath is the path to the binary file (which has no extension) + binPath string + // only fs knows how to handle metadata and versions + fs *cephfs + // a context with a user + ctx context.Context +} + +// GetInfo returns the FileInfo +func (upload *fileUpload) GetInfo(ctx context.Context) (tusd.FileInfo, error) { + return upload.info, nil +} + +// GetReader returns an io.Reader for the upload +func (upload *fileUpload) GetReader(ctx context.Context) (file io.Reader, err error) { + user := upload.fs.makeUser(upload.ctx) + user.op(func(cv *cacheVal) { + file, err = cv.mount.Open(upload.binPath, os.O_RDONLY, 0) + }) + return +} + +// WriteChunk writes the stream from the reader to the given offset of the upload +func (upload *fileUpload) WriteChunk(ctx context.Context, offset int64, src io.Reader) (n int64, err error) { + var file io.WriteCloser + user := upload.fs.makeUser(upload.ctx) + user.op(func(cv *cacheVal) { + file, err = cv.mount.Open(upload.binPath, os.O_WRONLY|os.O_APPEND, 0) + }) + if err != nil { + return 0, err + } + defer file.Close() + + n, err = io.Copy(file, src) + + // If the HTTP PATCH request gets interrupted in the middle (e.g. because + // the user wants to pause the upload), Go's net/http returns an io.ErrUnexpectedEOF. + // However, for OwnCloudStore it's not important whether the stream has ended + // on purpose or accidentally. + if err != nil { + if err != io.ErrUnexpectedEOF { + return n, err + } + } + + upload.info.Offset += n + err = upload.writeInfo() + + return n, err +} + +// writeInfo updates the entire information. Everything will be overwritten. +func (upload *fileUpload) writeInfo() error { + data, err := json.Marshal(upload.info) + + if err != nil { + return err + } + user := upload.fs.makeUser(upload.ctx) + user.op(func(cv *cacheVal) { + var file io.WriteCloser + if file, err = cv.mount.Open(upload.infoPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, filePermDefault); err != nil { + return + } + defer file.Close() + + _, err = io.Copy(file, bytes.NewReader(data)) + }) + + return err +} + +// FinishUpload finishes an upload and moves the file to the internal destination +func (upload *fileUpload) FinishUpload(ctx context.Context) (err error) { + + np := upload.info.Storage["InternalDestination"] + + // TODO check etag with If-Match header + // if destination exists + // if _, err := os.Stat(np); err == nil { + // the local storage does not store metadata + // the fileid is based on the path, so no we do not need to copy it to the new file + // the local storage does not track revisions + // } + + // if destination exists + // if _, err := os.Stat(np); err == nil { + // create revision + // if err := upload.fs.archiveRevision(upload.ctx, np); err != nil { + // return err + // } + // } + + user := upload.fs.makeUser(upload.ctx) + log := appctx.GetLogger(ctx) + + user.op(func(cv *cacheVal) { + err = cv.mount.Rename(upload.binPath, np) + }) + if err != nil { + return errors.Wrap(err, upload.binPath) + } + + // only delete the upload if it was successfully written to the fs + user.op(func(cv *cacheVal) { + err = cv.mount.Unlink(upload.infoPath) + }) + if err != nil { + if err.Error() != errNotFound { + log.Err(err).Interface("info", upload.info).Msg("cephfs: could not delete upload metadata") + } + } + + // TODO: set mtime if specified in metadata + + return +} + +// To implement the termination extension as specified in https://tus.io/protocols/resumable-upload.html#termination +// - the storage needs to implement AsTerminatableUpload +// - the upload needs to implement Terminate + +// AsTerminatableUpload returns a a TerminatableUpload +func (fs *cephfs) AsTerminatableUpload(upload tusd.Upload) tusd.TerminatableUpload { + return upload.(*fileUpload) +} + +// Terminate terminates the upload +func (upload *fileUpload) Terminate(ctx context.Context) (err error) { + user := upload.fs.makeUser(upload.ctx) + + user.op(func(cv *cacheVal) { + if err = cv.mount.Unlink(upload.infoPath); err != nil { + return + } + err = cv.mount.Unlink(upload.binPath) + }) + + return +} diff --git a/pkg/storage/fs/cephfs/user.go b/pkg/storage/fs/cephfs/user.go new file mode 100644 index 00000000000..e99686adfd7 --- /dev/null +++ b/pkg/storage/fs/cephfs/user.go @@ -0,0 +1,242 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +//go:build ceph +// +build ceph + +package cephfs + +import ( + "context" + "fmt" + "path/filepath" + "strconv" + "strings" + "syscall" + + "github.com/cs3org/reva/pkg/errtypes" + + cephfs2 "github.com/ceph/go-ceph/cephfs" + userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + ctx2 "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/mime" + "github.com/cs3org/reva/pkg/storage/utils/templates" + "github.com/pkg/errors" +) + +type callBack func(cb *cacheVal) + +// User custom type to add functionality to current struct +type User struct { + *userv1beta1.User + fs *cephfs + ctx context.Context + home string +} + +func (fs *cephfs) makeUser(ctx context.Context) *User { + u := ctx2.ContextMustGetUser(ctx) + home := filepath.Join(fs.conf.Root, templates.WithUser(u, fs.conf.UserLayout)) + return &User{u, fs, ctx, home} +} + +func (user *User) absPath(path string) string { + //shares will always be absolute to avoid prepending the user path to the path of the file's owner + if !filepath.IsAbs(path) { + path = filepath.Join(user.home, path) + } + + return path +} + +func (user *User) op(cb callBack) { + conn := user.fs.conn + if err := conn.lock.Acquire(conn.ctx, 1); err != nil { + return + } + defer conn.lock.Release(1) + + val, found := conn.cache.Get(user.Id.OpaqueId) + if !found { + cvalue := newConn(user) + if cvalue != nil { + conn.cache.Set(user.Id.OpaqueId, cvalue, 1) + } else { + return + } + cb(cvalue) + return + } + + cb(val.(*cacheVal)) +} + +func (user *User) fileAsResourceInfo(cv *cacheVal, path string, stat *cephfs2.CephStatx, mdKeys []string) (ri *provider.ResourceInfo, err error) { + var ( + _type provider.ResourceType + target string + size uint64 + buf []byte + ) + + switch int(stat.Mode) & syscall.S_IFMT { + case syscall.S_IFDIR: + _type = provider.ResourceType_RESOURCE_TYPE_CONTAINER + if buf, err = cv.mount.GetXattr(path, "ceph.dir.rbytes"); err == nil { + size, err = strconv.ParseUint(string(buf), 10, 64) + } + case syscall.S_IFLNK: + _type = provider.ResourceType_RESOURCE_TYPE_SYMLINK + target, err = cv.mount.Readlink(path) + case syscall.S_IFREG: + _type = provider.ResourceType_RESOURCE_TYPE_FILE + size = stat.Size + default: + return nil, errors.New("cephfs: unknown entry type") + } + + if err != nil { + return + } + + var xattrs []string + keys := make(map[string]bool, len(mdKeys)) + for _, key := range mdKeys { + keys[key] = true + } + if keys["*"] || len(keys) == 0 { + mdKeys = []string{} + keys = map[string]bool{} + } + mx := make(map[string]string) + if xattrs, err = cv.mount.ListXattr(path); err == nil { + for _, xattr := range xattrs { + if len(mdKeys) == 0 || keys[xattr] { + if buf, err := cv.mount.GetXattr(path, xattr); err == nil { + mx[xattr] = string(buf) + } + } + } + } + + //TODO(tmourati): Add entry id logic here + + var etag string + if isDir(_type) { + rctime, _ := cv.mount.GetXattr(path, "ceph.dir.rctime") + etag = fmt.Sprint(stat.Inode) + ":" + string(rctime) + } else { + etag = fmt.Sprint(stat.Inode) + ":" + strconv.FormatInt(stat.Ctime.Sec, 10) + } + + mtime := &typesv1beta1.Timestamp{ + Seconds: uint64(stat.Mtime.Sec), + Nanos: uint32(stat.Mtime.Nsec), + } + + perms := getPermissionSet(user, stat, cv.mount, path) + + for key := range mx { + if !strings.HasPrefix(key, xattrUserNs) { + delete(mx, key) + } + } + + var checksum provider.ResourceChecksum + var md5 string + if _type == provider.ResourceType_RESOURCE_TYPE_FILE { + md5tsBA, err := cv.mount.GetXattr(path, xattrMd5ts) //local error inside if scope + if err == nil { + md5ts, _ := strconv.ParseInt(string(md5tsBA), 10, 64) + if stat.Mtime.Sec == md5ts { + md5BA, err := cv.mount.GetXattr(path, xattrMd5) + if err != nil { + md5, err = calcChecksum(path, cv.mount, stat) + } else { + md5 = string(md5BA) + } + } else { + md5, err = calcChecksum(path, cv.mount, stat) + } + } else { + md5, err = calcChecksum(path, cv.mount, stat) + } + + if err != nil && err.Error() == errPermissionDenied { + checksum.Type = provider.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_UNSET + } else if err != nil { + return nil, errors.New("cephfs: error calculating checksum of file") + } else { + checksum.Type = provider.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_MD5 + checksum.Sum = md5 + } + } else { + checksum.Type = provider.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_UNSET + } + + var ownerID *userv1beta1.UserId + if stat.Uid != 0 { + var owner *userv1beta1.User + if int64(stat.Uid) != user.UidNumber { + owner, err = user.fs.getUserByID(user.ctx, fmt.Sprint(stat.Uid)) + } else { + owner = user.User + } + + if owner == nil { + return nil, errors.New("cephfs: error getting owner of entry: " + path) + } + + ownerID = owner.Id + } else { + ownerID = &userv1beta1.UserId{OpaqueId: "root"} + } + + ri = &provider.ResourceInfo{ + Type: _type, + Id: &provider.ResourceId{OpaqueId: fmt.Sprint(stat.Inode)}, + Checksum: &checksum, + Etag: etag, + MimeType: mime.Detect(isDir(_type), path), + Mtime: mtime, + Path: path, + PermissionSet: perms, + Size: size, + Owner: ownerID, + Target: target, + ArbitraryMetadata: &provider.ArbitraryMetadata{Metadata: mx}, + } + + return +} + +func (user *User) resolveRef(ref *provider.Reference) (str string, err error) { + if ref == nil { + return "", fmt.Errorf("cephfs: nil reference") + } + + if str = ref.GetPath(); str == "" { + return "", errtypes.NotSupported("cephfs: entry IDs not currently supported") + } + + str = removeLeadingSlash(str) //path must be relative + + return +} diff --git a/pkg/storage/fs/cephfs/utils.go b/pkg/storage/fs/cephfs/utils.go new file mode 100644 index 00000000000..e25b3bc5fa8 --- /dev/null +++ b/pkg/storage/fs/cephfs/utils.go @@ -0,0 +1,245 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +//go:build ceph +// +build ceph + +package cephfs + +import ( + "crypto/md5" + "encoding/hex" + "fmt" + "io" + "os" + "path/filepath" + "strconv" + "strings" + + cephfs2 "github.com/ceph/go-ceph/cephfs" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" +) + +// Mount type +type Mount = *cephfs2.MountInfo + +// Statx type +type Statx = *cephfs2.CephStatx + +var dirPermFull = uint32(0777) +var dirPermDefault = uint32(0775) +var filePermDefault = uint32(0660) + +func closeDir(directory *cephfs2.Directory) { + if directory != nil { + _ = directory.Close() + } +} + +func closeFile(file *cephfs2.File) { + if file != nil { + _ = file.Close() + } +} + +func destroyCephConn(mt Mount, perm *cephfs2.UserPerm) *cacheVal { + if perm != nil { + perm.Destroy() + } + if mt != nil { + _ = mt.Release() + } + return nil +} + +func deleteFile(mount *cephfs2.MountInfo, path string) { + _ = mount.Unlink(path) +} + +func isDir(t provider.ResourceType) bool { + return t == provider.ResourceType_RESOURCE_TYPE_CONTAINER +} + +func (fs *cephfs) makeFIDPath(fid string) string { + return "" //filepath.Join(fs.conf.EIDFolder, fid) +} + +func (fs *cephfs) makeFID(absolutePath string, inode string) (rid *provider.ResourceId, err error) { + sum := md5.New() + sum.Write([]byte(absolutePath)) + fid := fmt.Sprintf("%s-%s", hex.EncodeToString(sum.Sum(nil)), inode) + rid = &provider.ResourceId{OpaqueId: fid} + + _ = fs.adminConn.adminMount.Link(absolutePath, fs.makeFIDPath(fid)) + _ = fs.adminConn.adminMount.SetXattr(absolutePath, xattrEID, []byte(fid), 0) + + return +} + +func (fs *cephfs) getFIDPath(cv *cacheVal, path string) (fid string, err error) { + var buffer []byte + if buffer, err = cv.mount.GetXattr(path, xattrEID); err != nil { + return + } + + return fs.makeFIDPath(string(buffer)), err +} + +func calcChecksum(filepath string, mt Mount, stat Statx) (checksum string, err error) { + file, err := mt.Open(filepath, os.O_RDONLY, 0) + defer closeFile(file) + if err != nil { + return + } + hash := md5.New() + if _, err = io.Copy(hash, file); err != nil { + return + } + checksum = hex.EncodeToString(hash.Sum(nil)) + // we don't care if they fail, the checksum will just be recalculated if an error happens + _ = mt.SetXattr(filepath, xattrMd5ts, []byte(strconv.FormatInt(stat.Mtime.Sec, 10)), 0) + _ = mt.SetXattr(filepath, xattrMd5, []byte(checksum), 0) + + return +} + +func resolveRevRef(mt Mount, ref *provider.Reference, revKey string) (str string, err error) { + var buf []byte + if ref.GetResourceId() != nil { + str, err = mt.Readlink(filepath.Join(snap, revKey, ref.ResourceId.OpaqueId)) + if err != nil { + return "", fmt.Errorf("cephfs: invalid reference %+v", ref) + } + } else if str = ref.GetPath(); str != "" { + buf, err = mt.GetXattr(str, xattrEID) + if err != nil { + return + } + str, err = mt.Readlink(filepath.Join(snap, revKey, string(buf))) + if err != nil { + return + } + } else { + return "", fmt.Errorf("cephfs: empty reference %+v", ref) + } + + return filepath.Join(snap, revKey, str), err +} + +func removeLeadingSlash(path string) string { + return filepath.Join(".", path) +} + +func addLeadingSlash(path string) string { + return filepath.Join("/", path) +} + +func in(lookup string, list []string) bool { + for _, item := range list { + if item == lookup { + return true + } + } + return false +} + +func pathGenerator(path string, reverse bool, str chan string) { + if reverse { + str <- path + for i := range path { + if path[len(path)-i-1] == filepath.Separator { + str <- path[:len(path)-i-1] + } + } + } else { + for i := range path { + if path[i] == filepath.Separator { + str <- path[:i] + } + } + str <- path + } + + close(str) +} + +func walkPath(path string, f func(string) error, reverse bool) (err error) { + paths := make(chan string) + go pathGenerator(path, reverse, paths) + for path := range paths { + if path == "" { + continue + } + if err = f(path); err != nil && err.Error() != errFileExists && err.Error() != errNotFound { + break + } else { + err = nil + } + } + + return +} + +func (fs *cephfs) writeIndex(oid string, value string) (err error) { + return fs.adminConn.radosIO.WriteFull(oid, []byte(value)) +} + +func (fs *cephfs) removeIndex(oid string) error { + return fs.adminConn.radosIO.Delete(oid) +} + +func (fs *cephfs) resolveIndex(oid string) (fullPath string, err error) { + var i int + var currPath strings.Builder + root := string(filepath.Separator) + offset := uint64(0) + io := fs.adminConn.radosIO + bsize := 4096 + buffer := make([]byte, bsize) + for { + for { //read object + i, err = io.Read(oid, buffer, offset) + offset += uint64(bsize) + currPath.Write(buffer) + if err == nil && i >= bsize { + buffer = buffer[:0] + continue + } else { + offset = 0 + break + } + } + if err != nil { + return + } + + ss := strings.SplitN(currPath.String(), string(filepath.Separator), 2) + if len(ss) != 2 { + if currPath.String() == root { + return + } + + return "", fmt.Errorf("cephfs: entry id is not in the form of \"parentID/entryname\"") + } + parentOID := ss[0] + entryName := ss[1] + fullPath = filepath.Join(entryName, fullPath) + oid = parentOID + currPath.Reset() + } +} diff --git a/pkg/storage/fs/loader/loader.go b/pkg/storage/fs/loader/loader.go index f9ff86af040..cd88c5ddc79 100644 --- a/pkg/storage/fs/loader/loader.go +++ b/pkg/storage/fs/loader/loader.go @@ -20,6 +20,7 @@ package loader import ( // Load core storage filesystem backends. + _ "github.com/cs3org/reva/pkg/storage/fs/cephfs" _ "github.com/cs3org/reva/pkg/storage/fs/eos" _ "github.com/cs3org/reva/pkg/storage/fs/eosgrpc" _ "github.com/cs3org/reva/pkg/storage/fs/eosgrpchome" diff --git a/pkg/storage/fs/nextcloud/nextcloud.go b/pkg/storage/fs/nextcloud/nextcloud.go index e29806bc264..1750cd968c8 100644 --- a/pkg/storage/fs/nextcloud/nextcloud.go +++ b/pkg/storage/fs/nextcloud/nextcloud.go @@ -791,7 +791,7 @@ func (nc *StorageDriver) Unlock(ctx context.Context, ref *provider.Reference) er } // ListStorageSpaces as defined in the storage.FS interface -func (nc *StorageDriver) ListStorageSpaces(ctx context.Context, f []*provider.ListStorageSpacesRequest_Filter, _ map[string]struct{}) ([]*provider.StorageSpace, error) { +func (nc *StorageDriver) ListStorageSpaces(ctx context.Context, f []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { bodyStr, _ := json.Marshal(f) _, respBody, err := nc.do(ctx, Action{"ListStorageSpaces", string(bodyStr)}) if err != nil { diff --git a/pkg/storage/fs/nextcloud/nextcloud_test.go b/pkg/storage/fs/nextcloud/nextcloud_test.go index e155d64fec4..fb081b05923 100644 --- a/pkg/storage/fs/nextcloud/nextcloud_test.go +++ b/pkg/storage/fs/nextcloud/nextcloud_test.go @@ -981,7 +981,7 @@ var _ = Describe("Nextcloud", func() { }, } filters := []*provider.ListStorageSpacesRequest_Filter{filter1, filter2, filter3} - spaces, err := nc.ListStorageSpaces(ctx, filters, nil) + spaces, err := nc.ListStorageSpaces(ctx, filters) Expect(err).ToNot(HaveOccurred()) Expect(len(spaces)).To(Equal(1)) // https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L1341-L1366 diff --git a/pkg/storage/fs/owncloud/owncloud.go b/pkg/storage/fs/owncloud/owncloud.go index 84ab225d56c..82ee727b85d 100644 --- a/pkg/storage/fs/owncloud/owncloud.go +++ b/pkg/storage/fs/owncloud/owncloud.go @@ -2298,7 +2298,7 @@ func (fs *ocfs) RestoreRecycleItem(ctx context.Context, ref *provider.Reference, return fs.propagate(ctx, tgt) } -func (fs *ocfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, _ map[string]struct{}) ([]*provider.StorageSpace, error) { +func (fs *ocfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { var ( spaceType = spaceTypeAny diff --git a/pkg/storage/fs/owncloudsql/owncloudsql.go b/pkg/storage/fs/owncloudsql/owncloudsql.go index d9d92c340d4..b9579db28d0 100644 --- a/pkg/storage/fs/owncloudsql/owncloudsql.go +++ b/pkg/storage/fs/owncloudsql/owncloudsql.go @@ -1950,7 +1950,7 @@ func (fs *owncloudsqlfs) HashFile(path string) (string, string, string, error) { } } -func (fs *owncloudsqlfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, _ map[string]struct{}) ([]*provider.StorageSpace, error) { +func (fs *owncloudsqlfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { // TODO(corby): Implement return nil, errtypes.NotSupported("list storage spaces") } diff --git a/pkg/storage/fs/s3/s3.go b/pkg/storage/fs/s3/s3.go index 4fac5e265c5..07ea589267b 100644 --- a/pkg/storage/fs/s3/s3.go +++ b/pkg/storage/fs/s3/s3.go @@ -702,7 +702,7 @@ func (fs *s3FS) RestoreRecycleItem(ctx context.Context, ref *provider.Reference, return errtypes.NotSupported("restore recycle") } -func (fs *s3FS) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, _ map[string]struct{}) ([]*provider.StorageSpace, error) { +func (fs *s3FS) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { return nil, errtypes.NotSupported("list storage spaces") } diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 6bdc1726dd1..5c61f53234f 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -62,7 +62,7 @@ type FS interface { GetLock(ctx context.Context, ref *provider.Reference) (*provider.Lock, error) RefreshLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error Unlock(ctx context.Context, ref *provider.Reference) error - ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, permissions map[string]struct{}) ([]*provider.StorageSpace, error) + ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) error diff --git a/pkg/storage/utils/decomposedfs/decomposedfs.go b/pkg/storage/utils/decomposedfs/decomposedfs.go index 2397ea86c4f..686fd8977d5 100644 --- a/pkg/storage/utils/decomposedfs/decomposedfs.go +++ b/pkg/storage/utils/decomposedfs/decomposedfs.go @@ -39,6 +39,7 @@ import ( ctxpkg "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/logger" + "github.com/cs3org/reva/pkg/sharedconf" "github.com/cs3org/reva/pkg/storage" "github.com/cs3org/reva/pkg/storage/utils/chunking" "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/node" @@ -102,6 +103,8 @@ func NewDefault(m map[string]interface{}, bs tree.Blobstore) (storage.FS, error) lu.Options = o tp := tree.New(o.Root, o.TreeTimeAccounting, o.TreeSizeAccounting, lu, bs) + + o.GatewayAddr = sharedconf.GetGatewaySVC(o.GatewayAddr) return New(o, lu, p, tp) } diff --git a/pkg/storage/utils/decomposedfs/options/options.go b/pkg/storage/utils/decomposedfs/options/options.go index be5a2f21670..846cc039130 100644 --- a/pkg/storage/utils/decomposedfs/options/options.go +++ b/pkg/storage/utils/decomposedfs/options/options.go @@ -50,6 +50,8 @@ type Options struct { Owner string `mapstructure:"owner"` OwnerIDP string `mapstructure:"owner_idp"` OwnerType string `mapstructure:"owner_type"` + + GatewayAddr string `mapstructure:"gateway_addr"` } // New returns a new Options instance for the given configuration diff --git a/pkg/storage/utils/decomposedfs/spaces.go b/pkg/storage/utils/decomposedfs/spaces.go index 8cb869119a3..877718b239e 100644 --- a/pkg/storage/utils/decomposedfs/spaces.go +++ b/pkg/storage/utils/decomposedfs/spaces.go @@ -28,6 +28,7 @@ import ( "strings" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + permissionsv1beta1 "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" v1beta11 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" @@ -36,6 +37,7 @@ import ( ctxpkg "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/node" "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/xattrs" "github.com/cs3org/reva/pkg/utils" @@ -166,7 +168,7 @@ func (fs *Decomposedfs) CreateStorageSpace(ctx context.Context, req *provider.Cr // The list can be filtered by space type or space id. // Spaces are persisted with symlinks in /spaces// pointing to ../../nodes/, the root node of the space // The spaceid is a concatenation of storageid + "!" + nodeid -func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, permissions map[string]struct{}) ([]*provider.StorageSpace, error) { +func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { // TODO check filters // TODO when a space symlink is broken delete the space for cleanup @@ -233,6 +235,29 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide // the personal spaces must also use the nodeid and not the name numShares := 0 + client, err := pool.GetGatewayServiceClient(fs.o.GatewayAddr) + if err != nil { + return nil, err + } + + user := ctxpkg.ContextMustGetUser(ctx) + checkRes, err := client.CheckPermission(ctx, &permissionsv1beta1.CheckPermissionRequest{ + Permission: "list-all-spaces", + SubjectRef: &permissionsv1beta1.SubjectReference{ + Spec: &permissionsv1beta1.SubjectReference_UserId{ + UserId: user.Id, + }, + }, + }) + if err != nil { + return nil, err + } + + canListAllSpaces := false + if checkRes.Status.Code == v1beta11.Code_CODE_OK { + canListAllSpaces = true + } + for i := range matches { var target string var err error @@ -258,7 +283,7 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide } // TODO apply more filters - space, err := fs.storageSpaceFromNode(ctx, n, spaceType, matches[i], permissions) + space, err := fs.storageSpaceFromNode(ctx, n, spaceType, matches[i], canListAllSpaces) if err != nil { if _, ok := err.(errtypes.IsPermissionDenied); !ok { appctx.GetLogger(ctx).Error().Err(err).Interface("node", n).Msg("could not convert to storage space") @@ -277,7 +302,7 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide return nil, err } if n.Exists { - space, err := fs.storageSpaceFromNode(ctx, n, "*", n.InternalPath(), permissions) + space, err := fs.storageSpaceFromNode(ctx, n, "*", n.InternalPath(), canListAllSpaces) if err != nil { return nil, err } @@ -452,7 +477,7 @@ func (fs *Decomposedfs) createStorageSpace(ctx context.Context, spaceType, space return nil } -func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, spaceType, nodePath string, permissions map[string]struct{}) (*provider.StorageSpace, error) { +func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, spaceType, nodePath string, canListAllSpaces bool) (*provider.StorageSpace, error) { owner, err := n.Owner() if err != nil { return nil, err @@ -493,7 +518,6 @@ func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, } user := ctxpkg.ContextMustGetUser(ctx) - _, canListAllSpaces := permissions["list-all-spaces"] if !canListAllSpaces { ok, err := node.NewPermissions(fs.lu).HasPermission(ctx, n, func(p *provider.ResourcePermissions) bool { return p.Stat diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index 33fd5d46aa4..c2310e2a936 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -1628,7 +1628,7 @@ func (fs *eosfs) RestoreRecycleItem(ctx context.Context, ref *provider.Reference return fs.c.RestoreDeletedEntry(ctx, auth, key) } -func (fs *eosfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, _ map[string]struct{}) ([]*provider.StorageSpace, error) { +func (fs *eosfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { return nil, errtypes.NotSupported("list storage spaces") } diff --git a/pkg/storage/utils/localfs/localfs.go b/pkg/storage/utils/localfs/localfs.go index 5b5d3df4100..e6d3a19d44b 100644 --- a/pkg/storage/utils/localfs/localfs.go +++ b/pkg/storage/utils/localfs/localfs.go @@ -1290,7 +1290,7 @@ func (fs *localfs) RestoreRecycleItem(ctx context.Context, ref *provider.Referen return fs.propagate(ctx, localRestorePath) } -func (fs *localfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, _ map[string]struct{}) ([]*provider.StorageSpace, error) { +func (fs *localfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { return nil, errtypes.NotSupported("list storage spaces") } diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index 109506e37ab..509ee98f116 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -566,7 +566,7 @@ _requires a [CS3 user provisioning api that can update the quota for a user](htt - [apiShareOperationsToShares2/uploadToShare.feature:246](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L246) - [apiShareOperationsToShares2/uploadToShare.feature:270](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L270) - [apiShareOperationsToShares2/uploadToShare.feature:271](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L271) -- [apiShareOperationsToShares2/uploadToShare.feature:297](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L2597) +- [apiShareOperationsToShares2/uploadToShare.feature:297](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L297) - [apiShareOperationsToShares2/uploadToShare.feature:298](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L298) @@ -1304,4 +1304,4 @@ _ocs: api compatibility, return correct status code_ - [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L45) - [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L48) - [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L59) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L60) \ No newline at end of file +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L60) diff --git a/tests/integration/grpc/fixtures/gateway-sharded.toml b/tests/integration/grpc/fixtures/gateway-sharded.toml index bd4c1298e02..a74b3c41863 100644 --- a/tests/integration/grpc/fixtures/gateway-sharded.toml +++ b/tests/integration/grpc/fixtures/gateway-sharded.toml @@ -9,6 +9,7 @@ address = "{{grpc_address}}" # registries storageregistrysvc = "{{grpc_address}}" stat_cache_ttl = 1 +permissionssvc = "{{permissions_address}}" [grpc.services.authregistry] driver = "static" diff --git a/tests/integration/grpc/fixtures/gateway-static.toml b/tests/integration/grpc/fixtures/gateway-static.toml index df8a7abecfb..b8d1681e14f 100644 --- a/tests/integration/grpc/fixtures/gateway-static.toml +++ b/tests/integration/grpc/fixtures/gateway-static.toml @@ -9,6 +9,7 @@ address = "{{grpc_address}}" # registries storageregistrysvc = "{{grpc_address}}" stat_cache_ttl = 1 +permissionssvc = "{{permissions_address}}" [grpc.services.authregistry] driver = "static" diff --git a/tests/integration/grpc/fixtures/gateway.toml b/tests/integration/grpc/fixtures/gateway.toml index 8c08ca761d3..1aba40164f4 100644 --- a/tests/integration/grpc/fixtures/gateway.toml +++ b/tests/integration/grpc/fixtures/gateway.toml @@ -9,6 +9,7 @@ address = "{{grpc_address}}" # registries storageregistrysvc = "{{grpc_address}}" stat_cache_ttl = 1 +permissionssvc = "{{permissions_address}}" [grpc.services.authregistry] driver = "static" diff --git a/tests/integration/grpc/fixtures/permissions-ocis-ci.toml b/tests/integration/grpc/fixtures/permissions-ocis-ci.toml new file mode 100644 index 00000000000..00bdf854824 --- /dev/null +++ b/tests/integration/grpc/fixtures/permissions-ocis-ci.toml @@ -0,0 +1,12 @@ +# This config file will start a reva service that: +# - serves the ocis ci permissions service +[shared] +jwt_secret = "changemeplease" + +[grpc] +address = "{{grpc_address}}" + +[grpc.services.permissions] +driver = "demo" + +[grpc.services.permissions.drivers.ocisci] diff --git a/tests/integration/grpc/fixtures/storageprovider-ocis.toml b/tests/integration/grpc/fixtures/storageprovider-ocis.toml index e5c52002d6a..49c0dd1af56 100644 --- a/tests/integration/grpc/fixtures/storageprovider-ocis.toml +++ b/tests/integration/grpc/fixtures/storageprovider-ocis.toml @@ -9,4 +9,5 @@ root = "{{root}}/storage" treetime_accounting = true treesize_accounting = true enable_home = true -userprovidersvc = "localhost:18000" \ No newline at end of file +userprovidersvc = "localhost:18000" +gateway_addr = "{{gateway_address}}" \ No newline at end of file diff --git a/tests/integration/grpc/gateway_storageprovider_test.go b/tests/integration/grpc/gateway_storageprovider_test.go index b6e58950c0f..2fac29bdc1b 100644 --- a/tests/integration/grpc/gateway_storageprovider_test.go +++ b/tests/integration/grpc/gateway_storageprovider_test.go @@ -91,10 +91,11 @@ var _ = Describe("gateway", func() { BeforeEach(func() { dependencies = map[string]string{ - "gateway": "gateway.toml", - "users": "userprovider-json.toml", - "storage": "storageprovider-ocis.toml", - "storage2": "storageprovider-ocis.toml", + "gateway": "gateway.toml", + "users": "userprovider-json.toml", + "storage": "storageprovider-ocis.toml", + "storage2": "storageprovider-ocis.toml", + "permissions": "permissions-ocis-ci.toml", } }) @@ -167,6 +168,7 @@ var _ = Describe("gateway", func() { "homestorage": "storageprovider-ocis.toml", "storage": "storageprovider-ocis.toml", "storage2": "storageprovider-ocis.toml", + "permissions": "permissions-ocis-ci.toml", } }) @@ -355,10 +357,11 @@ var _ = Describe("gateway", func() { BeforeEach(func() { dependencies = map[string]string{ - "gateway": "gateway.toml", - "users": "userprovider-json.toml", - "storage": "storageprovider-ocis.toml", - "storage2": "storageprovider-ocis.toml", + "gateway": "gateway.toml", + "users": "userprovider-json.toml", + "storage": "storageprovider-ocis.toml", + "storage2": "storageprovider-ocis.toml", + "permissions": "permissions-ocis-ci.toml", } }) @@ -367,6 +370,7 @@ var _ = Describe("gateway", func() { fs, err = ocis.New(map[string]interface{}{ "root": revads["storage"].StorageRoot, "userprovidersvc": revads["users"].GrpcAddress, + "gateway_addr": revads["gateway"].GrpcAddress, "enable_home": true, "treesize_accounting": true, "treetime_accounting": true, @@ -377,7 +381,7 @@ var _ = Describe("gateway", func() { Expect(err).ToNot(HaveOccurred()) Expect(r.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) - spaces, err := fs.ListStorageSpaces(ctx, []*storagep.ListStorageSpacesRequest_Filter{}, nil) + spaces, err := fs.ListStorageSpaces(ctx, []*storagep.ListStorageSpacesRequest_Filter{}) Expect(err).ToNot(HaveOccurred()) homeSpace = spaces[0] diff --git a/tests/oc-integration-tests/drone/gateway.toml b/tests/oc-integration-tests/drone/gateway.toml index 82ebbb5ec45..fb8e896fcca 100644 --- a/tests/oc-integration-tests/drone/gateway.toml +++ b/tests/oc-integration-tests/drone/gateway.toml @@ -30,6 +30,8 @@ ocmcoresvc = "localhost:14000" ocmshareprovidersvc = "localhost:14000" ocminvitemanagersvc = "localhost:14000" ocmproviderauthorizersvc = "localhost:14000" +# permissions +permissionssvc = "localhost:10000" # other commit_share_to_storage_grant = true commit_share_to_storage_ref = true diff --git a/tests/oc-integration-tests/drone/permissions-ocis-ci.toml b/tests/oc-integration-tests/drone/permissions-ocis-ci.toml new file mode 100644 index 00000000000..489460b82f9 --- /dev/null +++ b/tests/oc-integration-tests/drone/permissions-ocis-ci.toml @@ -0,0 +1,12 @@ +# This config file will start a reva service that: +# - serves the ocis ci permissions service +[shared] +jwt_secret = "Pive-Fumkiu4" + +[grpc] +address = "0.0.0.0:10000" + +[grpc.services.permissions] +driver = "demo" + +[grpc.services.permissions.drivers.demo] diff --git a/tests/oc-integration-tests/drone/storage-users-ocis.toml b/tests/oc-integration-tests/drone/storage-users-ocis.toml index c767ae7659a..0cb5a88f86e 100644 --- a/tests/oc-integration-tests/drone/storage-users-ocis.toml +++ b/tests/oc-integration-tests/drone/storage-users-ocis.toml @@ -17,11 +17,13 @@ address = "0.0.0.0:11000" driver = "ocis" expose_data_server = true data_server_url = "http://revad-services:11001/data" +gateway_addr = "0.0.0.0:19000" [grpc.services.storageprovider.drivers.ocis] root = "/drone/src/tmp/reva/data" treetime_accounting = true treesize_accounting = true +gateway_addr = "0.0.0.0:19000" # we have a locally running dataprovider [http] diff --git a/tests/oc-integration-tests/local/gateway.toml b/tests/oc-integration-tests/local/gateway.toml index ae69da4a2f0..0f3d8166ee5 100644 --- a/tests/oc-integration-tests/local/gateway.toml +++ b/tests/oc-integration-tests/local/gateway.toml @@ -34,6 +34,8 @@ ocmcoresvc = "localhost:14000" ocmshareprovidersvc = "localhost:14000" ocminvitemanagersvc = "localhost:14000" ocmproviderauthorizersvc = "localhost:14000" +# permissions +permissionssvc = "localhost:10000" # other commit_share_to_storage_grant = true commit_share_to_storage_ref = true diff --git a/tests/oc-integration-tests/local/permissions-ocis-ci.toml b/tests/oc-integration-tests/local/permissions-ocis-ci.toml new file mode 100644 index 00000000000..489460b82f9 --- /dev/null +++ b/tests/oc-integration-tests/local/permissions-ocis-ci.toml @@ -0,0 +1,12 @@ +# This config file will start a reva service that: +# - serves the ocis ci permissions service +[shared] +jwt_secret = "Pive-Fumkiu4" + +[grpc] +address = "0.0.0.0:10000" + +[grpc.services.permissions] +driver = "demo" + +[grpc.services.permissions.drivers.demo] diff --git a/tests/oc-integration-tests/local/storage-users.toml b/tests/oc-integration-tests/local/storage-users.toml index 693300852af..350cd8ef6ec 100644 --- a/tests/oc-integration-tests/local/storage-users.toml +++ b/tests/oc-integration-tests/local/storage-users.toml @@ -39,3 +39,4 @@ temp_folder = "/var/tmp/reva/tmp" root = "/var/tmp/reva/data" treetime_accounting = true treesize_accounting = true +gateway_addr = "0.0.0.0:19000" From fd5aef5b2a78e08b4bd1b0d5f064711be1605f98 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Fri, 28 Jan 2022 00:26:51 +0545 Subject: [PATCH 23/49] Add end-of-line to expected-failures files (#2483) --- tests/acceptance/expected-failures-on-OCIS-storage.md | 3 +++ tests/acceptance/expected-failures-on-S3NG-storage.md | 3 +++ 2 files changed, 6 insertions(+) diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index 9b3b9d206a0..1b6ba79d18e 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -1303,3 +1303,6 @@ _ocs: api compatibility, return correct status code_ - [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L48) - [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L59) - [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L60) + +Note: always have an empty line at the end of this file. +The bash script that processes this file may not process a scenario reference on the last line. diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index 509ee98f116..ac2b12f380d 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -1305,3 +1305,6 @@ _ocs: api compatibility, return correct status code_ - [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L48) - [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L59) - [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L60) + +Note: always have an empty line at the end of this file. +The bash script that processes this file may not process a scenario reference on the last line. From 52630abc1e179a13fa6b2d6021ee92c4e2e36729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 28 Jan 2022 09:20:13 +0100 Subject: [PATCH 24/49] update cs3apis to include lock api changes (#2487) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- changelog/unreleased/update-cs3-lock-api.md | 3 +++ go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 changelog/unreleased/update-cs3-lock-api.md diff --git a/changelog/unreleased/update-cs3-lock-api.md b/changelog/unreleased/update-cs3-lock-api.md new file mode 100644 index 00000000000..7b6a2d3f3d6 --- /dev/null +++ b/changelog/unreleased/update-cs3-lock-api.md @@ -0,0 +1,3 @@ +Enhancement: update cs3apis to include lock api changes + +https://github.com/cs3org/reva/pull/2486 \ No newline at end of file diff --git a/go.mod b/go.mod index d1fb1c322d2..21feb75ecda 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/cheggaaa/pb v1.0.29 github.com/coreos/go-oidc v2.2.1+incompatible github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e - github.com/cs3org/go-cs3apis v0.0.0-20211214102128-4e8745ab1654 + github.com/cs3org/go-cs3apis v0.0.0-20220126114148-64c025ccdd19 github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 github.com/dgraph-io/ristretto v0.1.0 github.com/eventials/go-tus v0.0.0-20200718001131-45c7ec8f5d59 diff --git a/go.sum b/go.sum index 56a3789f7b4..3c41296610b 100644 --- a/go.sum +++ b/go.sum @@ -136,8 +136,8 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e h1:tqSPWQeueWTKnJVMJffz4pz0o1WuQxJ28+5x5JgaHD8= github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e/go.mod h1:XJEZ3/EQuI3BXTp/6DUzFr850vlxq11I6satRtz0YQ4= -github.com/cs3org/go-cs3apis v0.0.0-20211214102128-4e8745ab1654 h1:ha5tiuuFyDrwKUrVEc3TrRDFgTKVQ9NGDRmEP0PRPno= -github.com/cs3org/go-cs3apis v0.0.0-20211214102128-4e8745ab1654/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= +github.com/cs3org/go-cs3apis v0.0.0-20220126114148-64c025ccdd19 h1:1jqPH58jCxvbaJ9WLIJ7W2/m622bWS6ChptzljSG6IQ= +github.com/cs3org/go-cs3apis v0.0.0-20220126114148-64c025ccdd19/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 h1:Z9lwXumT5ACSmJ7WGnFl+OMLLjpz5uR2fyz7dC255FI= github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8/go.mod h1:4abs/jPXcmJzYoYGF91JF9Uq9s/KL5n1jvFDix8KcqY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= From 376754b59e59f843eb60bd3a28065c8f6744e459 Mon Sep 17 00:00:00 2001 From: Andre Duffeck Date: Fri, 28 Jan 2022 15:32:48 +0100 Subject: [PATCH 25/49] Make owncloudsql spaces aware (#2472) * Add ListStorages method * Implement ListStorageSpaces in owncloudsql * ResourceInfos do no longer contain the full path but only the basename * Handle references relative to a root * Fix space lookup, extract space functionality into a separate file * Use oc_mounts to find the storage roots This way it also works with storages with hashed IDs (that happens when the id exceeds a certain length). * Fix shares * Implement GetPath, fix GetPathById * Include the storage id when listing shares * Fix accepting declined shares * Ignore setting-grants-not-supported errors, storage grants are optional * Add changelog * Fix missing storage id from resource info * Fix field mask * Do not log error messages for unsupported grant calls * Fix hound issue * Fix changelog URL * Fix linter issue * Remove unfinished GetPath() code * Adapt expected failures * Cache user lookups in the oc10-sql share manager That leads to a massive performance boost. --- .../make-owncloudsql-spaces-aware.md | 5 + .../mocks/GatewayClient.go | 30 +++ .../sharesstorageprovider.go | 1 + .../storageprovider/storageprovider.go | 15 ++ .../sharing/shares/mocks/GatewayClient.go | 32 +++- .../handlers/apps/sharing/shares/pending.go | 16 +- .../apps/sharing/shares/pending_test.go | 6 +- .../handlers/apps/sharing/shares/shares.go | 3 +- pkg/share/manager/sql/conversions.go | 30 ++- pkg/share/manager/sql/sql.go | 24 ++- pkg/share/manager/sql/sql_test.go | 22 ++- .../fs/owncloudsql/filecache/filecache.go | 124 ++++++++---- .../owncloudsql/filecache/filecache_test.go | 52 +++++ pkg/storage/fs/owncloudsql/owncloudsql.go | 29 +-- pkg/storage/fs/owncloudsql/spaces.go | 179 ++++++++++++++++++ .../expected-failures-on-OCIS-storage.md | 6 + .../expected-failures-on-S3NG-storage.md | 6 + 17 files changed, 497 insertions(+), 83 deletions(-) create mode 100644 changelog/unreleased/make-owncloudsql-spaces-aware.md create mode 100644 pkg/storage/fs/owncloudsql/spaces.go diff --git a/changelog/unreleased/make-owncloudsql-spaces-aware.md b/changelog/unreleased/make-owncloudsql-spaces-aware.md new file mode 100644 index 00000000000..0039893dd7e --- /dev/null +++ b/changelog/unreleased/make-owncloudsql-spaces-aware.md @@ -0,0 +1,5 @@ +Bugfix: make owncloudsql work with the spaces registry + +owncloudsql now works properly with the spaces registry. + +https://github.com/cs3org/reva/pull/2372 diff --git a/internal/grpc/services/sharesstorageprovider/mocks/GatewayClient.go b/internal/grpc/services/sharesstorageprovider/mocks/GatewayClient.go index 3ebf3a3c1a1..5538440905f 100644 --- a/internal/grpc/services/sharesstorageprovider/mocks/GatewayClient.go +++ b/internal/grpc/services/sharesstorageprovider/mocks/GatewayClient.go @@ -96,6 +96,36 @@ func (_m *GatewayClient) Delete(ctx context.Context, in *providerv1beta1.DeleteR return r0, r1 } +// GetPath provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetPath(ctx context.Context, in *providerv1beta1.GetPathRequest, opts ...grpc.CallOption) (*providerv1beta1.GetPathResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *providerv1beta1.GetPathResponse + if rf, ok := ret.Get(0).(func(context.Context, *providerv1beta1.GetPathRequest, ...grpc.CallOption) *providerv1beta1.GetPathResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*providerv1beta1.GetPathResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *providerv1beta1.GetPathRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // InitiateFileDownload provides a mock function with given fields: ctx, req, opts func (_m *GatewayClient) InitiateFileDownload(ctx context.Context, req *providerv1beta1.InitiateFileDownloadRequest, opts ...grpc.CallOption) (*gatewayv1beta1.InitiateFileDownloadResponse, error) { _va := make([]interface{}, len(opts)) diff --git a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go index d0a17f138ff..2aae3fd8bf1 100644 --- a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go +++ b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider.go @@ -50,6 +50,7 @@ import ( // GatewayClient describe the interface of a gateway client type GatewayClient interface { + GetPath(ctx context.Context, in *provider.GetPathRequest, opts ...grpc.CallOption) (*provider.GetPathResponse, error) Stat(ctx context.Context, in *provider.StatRequest, opts ...grpc.CallOption) (*provider.StatResponse, error) Move(ctx context.Context, in *provider.MoveRequest, opts ...grpc.CallOption) (*provider.MoveResponse, error) Delete(ctx context.Context, in *provider.DeleteRequest, opts ...grpc.CallOption) (*provider.DeleteResponse, error) diff --git a/internal/grpc/services/storageprovider/storageprovider.go b/internal/grpc/services/storageprovider/storageprovider.go index 6815669964a..8219d2178e0 100644 --- a/internal/grpc/services/storageprovider/storageprovider.go +++ b/internal/grpc/services/storageprovider/storageprovider.go @@ -1090,6 +1090,11 @@ func (s *service) DenyGrant(ctx context.Context, req *provider.DenyGrantRequest) if err != nil { var st *rpc.Status switch err.(type) { + case errtypes.NotSupported: + // ignore - setting storage grants is optional + return &provider.DenyGrantResponse{ + Status: status.NewOK(ctx), + }, nil case errtypes.IsNotFound: st = status.NewNotFound(ctx, "path not found when setting grants") case errtypes.PermissionDenied: @@ -1135,6 +1140,11 @@ func (s *service) AddGrant(ctx context.Context, req *provider.AddGrantRequest) ( if err != nil { var st *rpc.Status switch err.(type) { + case errtypes.NotSupported: + // ignore - setting storage grants is optional + return &provider.AddGrantResponse{ + Status: status.NewOK(ctx), + }, nil case errtypes.IsNotFound: st = status.NewNotFound(ctx, "path not found when setting grants") case errtypes.PermissionDenied: @@ -1170,6 +1180,11 @@ func (s *service) UpdateGrant(ctx context.Context, req *provider.UpdateGrantRequ if err := s.storage.UpdateGrant(ctx, req.Ref, req.Grant); err != nil { var st *rpc.Status switch err.(type) { + case errtypes.NotSupported: + // ignore - setting storage grants is optional + return &provider.UpdateGrantResponse{ + Status: status.NewOK(ctx), + }, nil case errtypes.IsNotFound: st = status.NewNotFound(ctx, "path not found when updating grant") case errtypes.PermissionDenied: diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/mocks/GatewayClient.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/mocks/GatewayClient.go index d802734e9e1..b0aaf7962e0 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/mocks/GatewayClient.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/mocks/GatewayClient.go @@ -16,7 +16,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -// Code generated by mockery v1.1.2. DO NOT EDIT. +// Code generated by mockery v1.0.0. DO NOT EDIT. package mocks @@ -193,6 +193,36 @@ func (_m *GatewayClient) GetPath(ctx context.Context, in *providerv1beta1.GetPat return r0, r1 } +// GetReceivedShare provides a mock function with given fields: ctx, in, opts +func (_m *GatewayClient) GetReceivedShare(ctx context.Context, in *collaborationv1beta1.GetReceivedShareRequest, opts ...grpc.CallOption) (*collaborationv1beta1.GetReceivedShareResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *collaborationv1beta1.GetReceivedShareResponse + if rf, ok := ret.Get(0).(func(context.Context, *collaborationv1beta1.GetReceivedShareRequest, ...grpc.CallOption) *collaborationv1beta1.GetReceivedShareResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*collaborationv1beta1.GetReceivedShareResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *collaborationv1beta1.GetReceivedShareRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetShare provides a mock function with given fields: ctx, in, opts func (_m *GatewayClient) GetShare(ctx context.Context, in *collaborationv1beta1.GetShareRequest, opts ...grpc.CallOption) (*collaborationv1beta1.GetShareResponse, error) { _va := make([]interface{}, len(opts)) diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending.go index 9bc66874a21..8f74cb08c64 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending.go @@ -52,13 +52,13 @@ func (h *Handler) AcceptReceivedShare(w http.ResponseWriter, r *http.Request) { return } - share, ocsResponse := getShareFromID(ctx, client, shareID) + rs, ocsResponse := getReceivedShareFromID(ctx, client, shareID) if ocsResponse != nil { response.WriteOCSResponse(w, r, *ocsResponse, nil) return } - sharedResource, ocsResponse := getSharedResource(ctx, client, share) + sharedResource, ocsResponse := getSharedResource(ctx, client, rs.Share.Share.ResourceId) if ocsResponse != nil { response.WriteOCSResponse(w, r, *ocsResponse, nil) return @@ -76,7 +76,7 @@ func (h *Handler) AcceptReceivedShare(w http.ResponseWriter, r *http.Request) { var mountPoints []string sharesToAccept := map[string]bool{shareID: true} for _, s := range lrs.Shares { - if utils.ResourceIDEqual(s.Share.ResourceId, share.Share.GetResourceId()) { + if utils.ResourceIDEqual(s.Share.ResourceId, rs.Share.Share.GetResourceId()) { if s.State == collaboration.ShareState_SHARE_STATE_ACCEPTED { mount = s.MountPoint.Path } else { @@ -183,9 +183,9 @@ func (h *Handler) updateReceivedShare(w http.ResponseWriter, r *http.Request, sh response.WriteOCSSuccess(w, r, []*conversions.ShareData{data}) } -// getShareFromID uses a client to the gateway to fetch a share based on its ID. -func getShareFromID(ctx context.Context, client GatewayClient, shareID string) (*collaboration.GetShareResponse, *response.Response) { - s, err := client.GetShare(ctx, &collaboration.GetShareRequest{ +// getReceivedShareFromID uses a client to the gateway to fetch a share based on its ID. +func getReceivedShareFromID(ctx context.Context, client GatewayClient, shareID string) (*collaboration.GetReceivedShareResponse, *response.Response) { + s, err := client.GetReceivedShare(ctx, &collaboration.GetReceivedShareRequest{ Ref: &collaboration.ShareReference{ Spec: &collaboration.ShareReference_Id{ Id: &collaboration.ShareId{ @@ -213,10 +213,10 @@ func getShareFromID(ctx context.Context, client GatewayClient, shareID string) ( } // getSharedResource attempts to get a shared resource from the storage from the resource reference. -func getSharedResource(ctx context.Context, client GatewayClient, share *collaboration.GetShareResponse) (*provider.StatResponse, *response.Response) { +func getSharedResource(ctx context.Context, client GatewayClient, resID *provider.ResourceId) (*provider.StatResponse, *response.Response) { res, err := client.Stat(ctx, &provider.StatRequest{ Ref: &provider.Reference{ - ResourceId: share.Share.GetResourceId(), + ResourceId: resID, }, }) if err != nil { diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending_test.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending_test.go index 1412d19af58..8f130da31dc 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending_test.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending_test.go @@ -115,9 +115,11 @@ var _ = Describe("The ocs API", func() { ) BeforeEach(func() { - client.On("GetShare", mock.Anything, mock.Anything).Return(&collaboration.GetShareResponse{ + client.On("GetReceivedShare", mock.Anything, mock.Anything).Return(&collaboration.GetReceivedShareResponse{ Status: status.NewOK(context.Background()), - Share: share, + Share: &collaboration.ReceivedShare{ + Share: share, + }, }, nil) client.On("Stat", mock.Anything, mock.Anything).Return(&provider.StatResponse{ 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 a5f0ab4bf0b..ab980335376 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 @@ -117,6 +117,7 @@ type GatewayClient interface { ListShares(ctx context.Context, in *collaboration.ListSharesRequest, opts ...grpc.CallOption) (*collaboration.ListSharesResponse, error) GetShare(ctx context.Context, in *collaboration.GetShareRequest, opts ...grpc.CallOption) (*collaboration.GetShareResponse, error) + GetReceivedShare(ctx context.Context, in *collaboration.GetReceivedShareRequest, opts ...grpc.CallOption) (*collaboration.GetReceivedShareResponse, error) CreateShare(ctx context.Context, in *collaboration.CreateShareRequest, opts ...grpc.CallOption) (*collaboration.CreateShareResponse, error) RemoveShare(ctx context.Context, in *collaboration.RemoveShareRequest, opts ...grpc.CallOption) (*collaboration.RemoveShareResponse, error) ListReceivedShares(ctx context.Context, in *collaboration.ListReceivedSharesRequest, opts ...grpc.CallOption) (*collaboration.ListReceivedSharesResponse, error) @@ -329,7 +330,7 @@ func (h *Handler) CreateShare(w http.ResponseWriter, r *http.Request) { MountPoint: s.MountPoint, State: collaboration.ShareState_SHARE_STATE_ACCEPTED, }, - UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"state, mount_point"}}, + UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"state", "mount_point"}}, } shareRes, err := client.UpdateReceivedShare(granteeCtx, updateRequest) diff --git a/pkg/share/manager/sql/conversions.go b/pkg/share/manager/sql/conversions.go index 28c28f39711..84772f658f5 100644 --- a/pkg/share/manager/sql/conversions.go +++ b/pkg/share/manager/sql/conversions.go @@ -21,7 +21,9 @@ package sql import ( "context" "strings" + "time" + "github.com/ReneKroon/ttlcache/v2" grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" userprovider "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" @@ -65,17 +67,34 @@ type UserConverter interface { // GatewayUserConverter converts usernames and ids using the gateway type GatewayUserConverter struct { gwAddr string + + IDCache *ttlcache.Cache + NameCache *ttlcache.Cache } // NewGatewayUserConverter returns a instance of GatewayUserConverter func NewGatewayUserConverter(gwAddr string) *GatewayUserConverter { + IDCache := ttlcache.NewCache() + _ = IDCache.SetTTL(30 * time.Second) + IDCache.SkipTTLExtensionOnHit(true) + NameCache := ttlcache.NewCache() + _ = NameCache.SetTTL(30 * time.Second) + NameCache.SkipTTLExtensionOnHit(true) + return &GatewayUserConverter{ - gwAddr: gwAddr, + gwAddr: gwAddr, + IDCache: IDCache, + NameCache: NameCache, } } // UserIDToUserName converts a user ID to an username func (c *GatewayUserConverter) UserIDToUserName(ctx context.Context, userid *userpb.UserId) (string, error) { + username, err := c.NameCache.Get(userid.String()) + if err == nil { + return username.(string), nil + } + gwConn, err := pool.GetGatewayServiceClient(c.gwAddr) if err != nil { return "", err @@ -89,11 +108,17 @@ func (c *GatewayUserConverter) UserIDToUserName(ctx context.Context, userid *use if getUserResponse.Status.Code != rpc.Code_CODE_OK { return "", status.NewErrorFromCode(getUserResponse.Status.Code, "gateway") } + _ = c.NameCache.Set(userid.String(), getUserResponse.User.Username) return getUserResponse.User.Username, nil } // UserNameToUserID converts a username to an user ID func (c *GatewayUserConverter) UserNameToUserID(ctx context.Context, username string) (*userpb.UserId, error) { + id, err := c.IDCache.Get(username) + if err == nil { + return id.(*userpb.UserId), nil + } + gwConn, err := pool.GetGatewayServiceClient(c.gwAddr) if err != nil { return nil, err @@ -108,6 +133,7 @@ func (c *GatewayUserConverter) UserNameToUserID(ctx context.Context, username st if getUserResponse.Status.Code != rpc.Code_CODE_OK { return nil, status.NewErrorFromCode(getUserResponse.Status.Code, "gateway") } + _ = c.IDCache.Set(username, getUserResponse.User.Id) return getUserResponse.User.Id, nil } @@ -233,7 +259,7 @@ func (m *mgr) convertToCS3Share(ctx context.Context, s DBShare, storageMountID s OpaqueId: s.ID, }, ResourceId: &provider.ResourceId{ - StorageId: storageMountID + "!" + s.ItemStorage, + StorageId: s.ItemStorage, OpaqueId: s.FileSource, }, Permissions: &collaboration.SharePermissions{Permissions: permissions}, diff --git a/pkg/share/manager/sql/sql.go b/pkg/share/manager/sql/sql.go index 74b31d7bb84..06ac74bb527 100644 --- a/pkg/share/manager/sql/sql.go +++ b/pkg/share/manager/sql/sql.go @@ -277,7 +277,15 @@ func (m *mgr) UpdateShare(ctx context.Context, ref *collaboration.ShareReference func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.Filter) ([]*collaboration.Share, error) { uid := ctxpkg.ContextMustGetUser(ctx).Username - query := "select coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator, coalesce(share_with, '') as share_with, coalesce(file_source, '') as file_source, file_target, id, stime, permissions, share_type FROM oc_share WHERE (uid_owner=? or uid_initiator=?)" + query := ` + SELECT + coalesce(s.uid_owner, '') as uid_owner, coalesce(s.uid_initiator, '') as uid_initiator, + coalesce(s.share_with, '') as share_with, coalesce(s.file_source, '') as file_source, + s.file_target, s.id, s.stime, s.permissions, s.share_type, fc.storage as storage + FROM oc_share s + LEFT JOIN oc_filecache fc ON fc.fileid = file_source + WHERE (uid_owner=? or uid_initiator=?) + ` params := []interface{}{uid, uid} var ( @@ -310,7 +318,7 @@ func (m *mgr) ListShares(ctx context.Context, filters []*collaboration.Filter) ( var s DBShare shares := []*collaboration.Share{} for rows.Next() { - if err := rows.Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.FileSource, &s.FileTarget, &s.ID, &s.STime, &s.Permissions, &s.ShareType); err != nil { + if err := rows.Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.FileSource, &s.FileTarget, &s.ID, &s.STime, &s.Permissions, &s.ShareType, &s.ItemStorage); err != nil { continue } share, err := m.convertToCS3Share(ctx, s, m.storageMountID) @@ -357,7 +365,7 @@ func (m *mgr) ListReceivedShares(ctx context.Context, filters []*collaboration.F ) SELECT COALESCE(r.uid_owner, '') AS uid_owner, COALESCE(r.uid_initiator, '') AS uid_initiator, COALESCE(r.share_with, '') AS share_with, COALESCE(r.file_source, '') AS file_source, COALESCE(r2.file_target, r.file_target), r.id, r.stime, r.permissions, r.share_type, COALESCE(r2.accepted, r.accepted), - r.numeric_id, COALESCE(r.parent, -1) AS parent FROM results r LEFT JOIN results r2 ON r.id = r2.parent WHERE r.parent IS NULL;` + r.numeric_id, COALESCE(r.parent, -1) AS parent FROM results r LEFT JOIN results r2 ON r.id = r2.parent WHERE r.parent IS NULL` filterQuery, filterParams, err := translateFilters(filters) if err != nil { @@ -368,7 +376,7 @@ func (m *mgr) ListReceivedShares(ctx context.Context, filters []*collaboration.F if filterQuery != "" { query = fmt.Sprintf("%s AND (%s)", query, filterQuery) } - + query += ";" rows, err := m.db.Query(query, params...) if err != nil { return nil, err @@ -543,7 +551,7 @@ func (m *mgr) getReceivedByID(ctx context.Context, id *collaboration.ShareId) (* SELECT s.*, storages.numeric_id FROM oc_share s LEFT JOIN oc_storages storages ON ` + homeConcat + ` - WHERE s.id=? OR s.parent=?` + userSelect + ` + WHERE s.id=? OR s.parent=? ` + userSelect + ` ) SELECT COALESCE(r.uid_owner, '') AS uid_owner, COALESCE(r.uid_initiator, '') AS uid_initiator, COALESCE(r.share_with, '') AS share_with, COALESCE(r.file_source, '') AS file_source, COALESCE(r2.file_target, r.file_target), r.id, r.stime, r.permissions, r.share_type, COALESCE(r2.accepted, r.accepted), @@ -621,7 +629,7 @@ func translateFilters(filters []*collaboration.Filter) (string, []interface{}, e case collaboration.Filter_TYPE_RESOURCE_ID: filterQuery += "(" for i, f := range filters { - filterQuery += "item_source=?" + filterQuery += "file_source=?" params = append(params, f.GetResourceId().OpaqueId) if i != len(filters)-1 { @@ -632,7 +640,7 @@ func translateFilters(filters []*collaboration.Filter) (string, []interface{}, e case collaboration.Filter_TYPE_GRANTEE_TYPE: filterQuery += "(" for i, f := range filters { - filterQuery += "share_type=?" + filterQuery += "r.share_type=?" params = append(params, granteeTypeToShareType(f.GetGranteeType())) if i != len(filters)-1 { @@ -642,7 +650,7 @@ func translateFilters(filters []*collaboration.Filter) (string, []interface{}, e filterQuery += ")" case collaboration.Filter_TYPE_EXCLUDE_DENIALS: // TODO this may change once the mapping of permission to share types is completed (cf. pkg/cbox/utils/conversions.go) - filterQuery += "permissions > 0" + filterQuery += "r.permissions > 0" default: return "", nil, fmt.Errorf("filter type is not supported") } diff --git a/pkg/share/manager/sql/sql_test.go b/pkg/share/manager/sql/sql_test.go index 24d20ede454..4f327ed6ad0 100644 --- a/pkg/share/manager/sql/sql_test.go +++ b/pkg/share/manager/sql/sql_test.go @@ -201,12 +201,19 @@ var _ = Describe("SQL manager", func() { shares, err := mgr.ListShares(ctx, []*collaboration.Filter{}) Expect(err).ToNot(HaveOccurred()) Expect(len(shares)).To(Equal(1)) + share := shares[0] + Expect(share.ResourceId.StorageId).To(Equal("1")) shares, err = mgr.ListShares(ctx, []*collaboration.Filter{ - share.ResourceIDFilter(&provider.ResourceId{ - StorageId: "/", - OpaqueId: "somethingElse", - }), + { + Type: collaboration.Filter_TYPE_RESOURCE_ID, + Term: &collaboration.Filter_ResourceId{ + ResourceId: &provider.ResourceId{ + StorageId: "/", + OpaqueId: "somethingElse", + }, + }, + }, }) Expect(err).ToNot(HaveOccurred()) Expect(len(shares)).To(Equal(0)) @@ -338,6 +345,13 @@ var _ = Describe("SQL manager", func() { Expect(err).ToNot(HaveOccurred()) Expect(len(shares)).To(Equal(1)) }) + + It("works with filters", func() { + loginAs(otherUser) + shares, err := mgr.ListReceivedShares(ctx, []*collaboration.Filter{{Type: collaboration.Filter_TYPE_EXCLUDE_DENIALS}}) + Expect(err).ToNot(HaveOccurred()) + Expect(len(shares)).To(Equal(1)) + }) }) Describe("GetReceivedShare", func() { diff --git a/pkg/storage/fs/owncloudsql/filecache/filecache.go b/pkg/storage/fs/owncloudsql/filecache/filecache.go index 7e0c58a1d88..49cb044c8e7 100644 --- a/pkg/storage/fs/owncloudsql/filecache/filecache.go +++ b/pkg/storage/fs/owncloudsql/filecache/filecache.go @@ -42,6 +42,46 @@ type Cache struct { db *sql.DB } +// Storage represents a storage entry in the database +type Storage struct { + ID string + NumericID int +} + +// File represents an entry of the file cache +type File struct { + ID int + Storage int + Parent int + MimePart int + MimeType int + MimeTypeString string + Size int + MTime int + StorageMTime int + UnencryptedSize int + Permissions int + Encrypted bool + Path string + Name string + Etag string + Checksum string +} + +// TrashItem represents a trash item of the file cache +type TrashItem struct { + ID int + Name string + User string + Path string + Timestamp int +} + +// Scannable describes the interface providing a Scan method +type Scannable interface { + Scan(...interface{}) error +} + // NewMysql returns a new Cache instance connecting to a MySQL database func NewMysql(dsn string) (*Cache, error) { sqldb, err := sql.Open("mysql", dsn) @@ -68,6 +108,56 @@ func New(driver string, sqldb *sql.DB) (*Cache, error) { }, nil } +// ListStorages returns the list of numeric ids of all storages +// Optionally only home storages are considered +func (c *Cache) ListStorages(onlyHome bool) ([]*Storage, error) { + query := "" + if onlyHome { + mountPointConcat := "" + if c.driver == "mysql" { + mountPointConcat = "m.mount_point = CONCAT('/', m.user_id, '/')" + } else { // sqlite3 + mountPointConcat = "m.mount_point = '/' || m.user_id || '/'" + } + + query = "SELECT s.id, s.numeric_id FROM oc_storages s JOIN oc_mounts m ON s.numeric_id = m.storage_id WHERE " + mountPointConcat + } else { + query = "SELECT id, numeric_id FROM oc_storages" + } + rows, err := c.db.Query(query) + if err != nil { + return nil, err + } + defer rows.Close() + + storages := []*Storage{} + for rows.Next() { + storage := &Storage{} + err := rows.Scan(&storage.ID, &storage.NumericID) + if err != nil { + return nil, err + } + storages = append(storages, storage) + } + return storages, nil +} + +// GetStorage returns the storage with the given numeric id +func (c *Cache) GetStorage(numeridID interface{}) (*Storage, error) { + numericID, err := toIntID(numeridID) + if err != nil { + return nil, err + } + row := c.db.QueryRow("SELECT id, numeric_id FROM oc_storages WHERE numeric_id = ?", numericID) + s := &Storage{} + switch err := row.Scan(&s.ID, &s.NumericID); err { + case nil: + return s, nil + default: + return nil, err + } +} + // GetNumericStorageID returns the database id for the given storage func (c *Cache) GetNumericStorageID(id string) (int, error) { row := c.db.QueryRow("SELECT numeric_id FROM oc_storages WHERE id = ?", id) @@ -153,40 +243,6 @@ func (c *Cache) GetStorageOwnerByFileID(numericID interface{}) (string, error) { } } -// File represents an entry of the file cache -type File struct { - ID int - Storage int - Parent int - MimePart int - MimeType int - MimeTypeString string - Size int - MTime int - StorageMTime int - UnencryptedSize int - Permissions int - Encrypted bool - Path string - Name string - Etag string - Checksum string -} - -// TrashItem represents a trash item of the file cache -type TrashItem struct { - ID int - Name string - User string - Path string - Timestamp int -} - -// Scannable describes the interface providing a Scan method -type Scannable interface { - Scan(...interface{}) error -} - func (c *Cache) rowToFile(row Scannable) (*File, error) { var fileid, storage, parent, mimetype, mimepart, size, mtime, storageMtime, encrypted, unencryptedSize int var permissions sql.NullInt32 diff --git a/pkg/storage/fs/owncloudsql/filecache/filecache_test.go b/pkg/storage/fs/owncloudsql/filecache/filecache_test.go index c1c383aa58e..4a6eab04381 100644 --- a/pkg/storage/fs/owncloudsql/filecache/filecache_test.go +++ b/pkg/storage/fs/owncloudsql/filecache/filecache_test.go @@ -63,6 +63,58 @@ var _ = Describe("Filecache", func() { os.Remove(testDbFile.Name()) }) + Describe("ListStorages", func() { + It("returns all storages", func() { + storages, err := cache.ListStorages(false) + Expect(err).ToNot(HaveOccurred()) + Expect(len(storages)).To(Equal(2)) + ids := []string{} + numericIDs := []int{} + for _, s := range storages { + ids = append(ids, s.ID) + numericIDs = append(numericIDs, s.NumericID) + } + Expect(numericIDs).To(ConsistOf([]int{1, 2})) + Expect(ids).To(ConsistOf([]string{"home::admin", "local::/mnt/data/files/"})) + }) + + It("returns all home storages", func() { + storages, err := cache.ListStorages(true) + Expect(err).ToNot(HaveOccurred()) + Expect(len(storages)).To(Equal(1)) + Expect(storages[0].ID).To(Equal("home::admin")) + Expect(storages[0].NumericID).To(Equal(1)) + }) + }) + + Describe("GetStorage", func() { + It("returns an error when the id is invalid", func() { + s, err := cache.GetStorage("foo") + Expect(err).To(HaveOccurred()) + Expect(s).To(BeNil()) + }) + + It("returns an error when the id doesn't exist", func() { + s, err := cache.GetStorage(100) + Expect(err).To(HaveOccurred()) + Expect(s).To(BeNil()) + }) + + It("returns the storage", func() { + s, err := cache.GetStorage(1) + Expect(err).ToNot(HaveOccurred()) + Expect(s.ID).To(Equal("home::admin")) + Expect(s.NumericID).To(Equal(1)) + }) + + It("takes string ids", func() { + s, err := cache.GetStorage("1") + Expect(err).ToNot(HaveOccurred()) + Expect(s.ID).To(Equal("home::admin")) + Expect(s.NumericID).To(Equal(1)) + }) + }) + Describe("GetNumericStorageID", func() { It("returns the proper storage id", func() { nid, err := cache.GetNumericStorageID("home::admin") diff --git a/pkg/storage/fs/owncloudsql/owncloudsql.go b/pkg/storage/fs/owncloudsql/owncloudsql.go index b9579db28d0..d0fe1d39cd9 100644 --- a/pkg/storage/fs/owncloudsql/owncloudsql.go +++ b/pkg/storage/fs/owncloudsql/owncloudsql.go @@ -489,11 +489,6 @@ func (fs *owncloudsqlfs) getUserStorage(user string) (int, error) { return id, err } -// CreateStorageSpace creates a storage space -func (fs *owncloudsqlfs) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) { - return nil, errtypes.NotSupported("unimplemented: CreateStorageSpace") -} - func (fs *owncloudsqlfs) convertToResourceInfo(ctx context.Context, entry *filecache.File, ip string, mdKeys []string) (*provider.ResourceInfo, error) { mdKeysMap := make(map[string]struct{}) for _, k := range mdKeys { @@ -507,8 +502,8 @@ func (fs *owncloudsqlfs) convertToResourceInfo(ctx context.Context, entry *filec isDir := entry.MimeTypeString == "httpd/unix-directory" ri := &provider.ResourceInfo{ - Id: &provider.ResourceId{OpaqueId: strconv.Itoa(entry.ID)}, - Path: fs.toStoragePath(ctx, ip), + Id: &provider.ResourceId{StorageId: strconv.Itoa(entry.Storage), OpaqueId: strconv.Itoa(entry.ID)}, + Path: filepath.Base(ip), Type: getResourceType(isDir), Etag: entry.Etag, MimeType: entry.MimeTypeString, @@ -544,7 +539,7 @@ func (fs *owncloudsqlfs) convertToResourceInfo(ctx context.Context, entry *filec // GetPathByID returns the storage relative path for the file id, without the internal namespace func (fs *owncloudsqlfs) GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error) { - ip, err := fs.filecache.Path(id.OpaqueId) + ip, err := fs.resolve(ctx, &provider.Reference{ResourceId: id}) if err != nil { return "", err } @@ -580,6 +575,9 @@ func (fs *owncloudsqlfs) resolve(ctx context.Context, ref *provider.Reference) ( } p = filepath.Join(owner, p) } + if ref.GetPath() != "" { + p = filepath.Join(p, ref.GetPath()) + } return fs.toInternalPath(ctx, p), nil } @@ -1950,21 +1948,6 @@ func (fs *owncloudsqlfs) HashFile(path string) (string, string, string, error) { } } -func (fs *owncloudsqlfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { - // TODO(corby): Implement - return nil, errtypes.NotSupported("list storage spaces") -} - -// UpdateStorageSpace updates a storage space -func (fs *owncloudsqlfs) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) { - return nil, errtypes.NotSupported("update storage space") -} - -// DeleteStorageSpace deletes a storage space -func (fs *owncloudsqlfs) DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) error { - return errtypes.NotSupported("delete storage space") -} - func readChecksumIntoResourceChecksum(ctx context.Context, checksums, algo string, ri *provider.ResourceInfo) { re := regexp.MustCompile(strings.ToUpper(algo) + `:(.*)`) matches := re.FindStringSubmatch(checksums) diff --git a/pkg/storage/fs/owncloudsql/spaces.go b/pkg/storage/fs/owncloudsql/spaces.go new file mode 100644 index 00000000000..f58d6e0ae15 --- /dev/null +++ b/pkg/storage/fs/owncloudsql/spaces.go @@ -0,0 +1,179 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package owncloudsql + +import ( + "context" + "fmt" + "strconv" + "strings" + + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/storage/fs/owncloudsql/filecache" + "github.com/cs3org/reva/pkg/utils" +) + +// ListStorageSpaces lists storage spaces according to the provided filters +func (fs *owncloudsqlfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { + var ( + spaceID = "*" + ) + + filteringUnsupportedSpaceTypes := false + + for i := range filter { + switch filter[i].Type { + case provider.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE: + t := filter[i].GetSpaceType() + filteringUnsupportedSpaceTypes = (t != "personal" && !strings.HasPrefix(t, "+")) + case provider.ListStorageSpacesRequest_Filter_TYPE_ID: + spaceID, _, _ = utils.SplitStorageSpaceID(filter[i].GetId().OpaqueId) + } + } + if filteringUnsupportedSpaceTypes { + // owncloudsql only supports personal spaces, no need to search for something else + return []*provider.StorageSpace{}, nil + } + + spaces := []*provider.StorageSpace{} + if spaceID == "*" { + u, ok := ctxpkg.ContextGetUser(ctx) + if !ok { + return nil, errtypes.UserRequired("error getting user from context") + } + space, err := fs.getPersonalSpace(u) + if err != nil { + return nil, err + } + spaces = append(spaces, space) + } else { + id, err := strconv.Atoi(spaceID) + if err != nil { + // non-numeric space id -> this request is not for us + return []*provider.StorageSpace{}, nil + } + space, err := fs.getSpaceByNumericID(ctx, id) + if err != nil { + return nil, err + } + spaces = append(spaces, space) + } + return spaces, nil +} + +// CreateStorageSpace creates a storage space +func (fs *owncloudsqlfs) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) { + return nil, errtypes.NotSupported("unimplemented: CreateStorageSpace") +} + +// UpdateStorageSpace updates a storage space +func (fs *owncloudsqlfs) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) { + return nil, errtypes.NotSupported("update storage space") +} + +// DeleteStorageSpace deletes a storage space +func (fs *owncloudsqlfs) DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) error { + return errtypes.NotSupported("delete storage space") +} + +// Note: currently unused but will be used later +// func (fs *owncloudsqlfs) listAllPersonalSpaces(ctx context.Context) ([]*provider.StorageSpace, error) { +// storages, err := fs.filecache.ListStorages(true) +// if err != nil { +// return nil, err +// } +// spaces := []*provider.StorageSpace{} +// for _, storage := range storages { +// space, err := fs.storageToSpace(ctx, storage) +// if err != nil { +// return nil, err +// } +// spaces = append(spaces, space) +// } +// return spaces, nil +// } + +func (fs *owncloudsqlfs) getPersonalSpace(owner *userpb.User) (*provider.StorageSpace, error) { + storageID, err := fs.filecache.GetNumericStorageID("home::" + owner.Username) + if err != nil { + return nil, err + } + storage, err := fs.filecache.GetStorage(storageID) + if err != nil { + return nil, err + } + root, err := fs.filecache.Get(storage.NumericID, "") + if err != nil { + return nil, err + } + + space := &provider.StorageSpace{ + Id: &provider.StorageSpaceId{OpaqueId: strconv.Itoa(storage.NumericID)}, + Root: &provider.ResourceId{ + StorageId: strconv.Itoa(storage.NumericID), + OpaqueId: strconv.Itoa(root.ID), + }, + Name: owner.Username, + SpaceType: "personal", + Mtime: &types.Timestamp{Seconds: uint64(root.MTime)}, + Owner: owner, + } + return space, nil +} + +func (fs *owncloudsqlfs) getSpaceByNumericID(ctx context.Context, spaceID int) (*provider.StorageSpace, error) { + storage, err := fs.filecache.GetStorage(spaceID) + if err != nil { + return nil, err + } + if !strings.HasPrefix(storage.ID, "home::") { + return nil, fmt.Errorf("only personal spaces are supported") + } + + return fs.storageToSpace(ctx, storage) +} + +func (fs *owncloudsqlfs) storageToSpace(ctx context.Context, storage *filecache.Storage) (*provider.StorageSpace, error) { + root, err := fs.filecache.Get(storage.NumericID, "") + if err != nil { + return nil, err + } + ownerName := strings.TrimPrefix(storage.ID, "home::") + owner, err := fs.getUser(ctx, ownerName) + if err != nil { + return nil, err + } + + space := &provider.StorageSpace{ + Id: &provider.StorageSpaceId{OpaqueId: strconv.Itoa(storage.NumericID)}, + Root: &provider.ResourceId{ + StorageId: strconv.Itoa(storage.NumericID), + OpaqueId: strconv.Itoa(root.ID), + }, + Name: owner.Username, + SpaceType: "personal", + Mtime: &types.Timestamp{Seconds: uint64(root.MTime)}, + Owner: owner, + } + return space, nil +} diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index 1b6ba79d18e..08cc6b6bde5 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -1156,6 +1156,12 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers #### moving a share from the /Shares jail to a user home is no longer supported. - [apiShareManagementToShares/mergeShare.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/mergeShare.feature#L89) +#### Additional shares to the same resource (e.g. user and group share) are now auto-accepted with the existing mountpoint +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:553](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:553) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:554](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:554) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:575](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:575) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:576](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:576) + ### To triage _The below features have been added after I last categorized them. AFAICT they are bugs. @jfd_ diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index ac2b12f380d..76d77eea0ed 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -1174,6 +1174,12 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers #### moving a share from the /Shares jail to a user home is no longer supported. - [apiShareManagementToShares/mergeShare.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/mergeShare.feature#L89) +#### Additional shares to the same resource (e.g. user and group share) are now auto-accepted with the existing mountpoint +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:553](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:553) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:554](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:554) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:575](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:575) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:576](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:576) + ### To triage _The below features have been added after I last categorized them. AFAICT they are bugs. @jfd_ From 1011a7b43e7ebd4cd3defa5eba01cd79e3c64c18 Mon Sep 17 00:00:00 2001 From: Amrita <54478846+amrita-shrestha@users.noreply.github.com> Date: Fri, 28 Jan 2022 20:35:07 +0545 Subject: [PATCH 26/49] Bump the commit id for tests (#2490) --- .drone.env | 2 +- .../expected-failures-on-OCIS-storage.md | 23 ++++++++++++------- .../expected-failures-on-S3NG-storage.md | 23 ++++++++++++------- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/.drone.env b/.drone.env index d0afb183e24..4f28f08f0f6 100644 --- a/.drone.env +++ b/.drone.env @@ -1,3 +1,3 @@ # The test runner source for API tests -CORE_COMMITID=65a12d1858c0708cb1ca2ad620e139e0d33b73ae +CORE_COMMITID=0dadfbe475438dd97c192cb93643ef8d95b71faa CORE_BRANCH=master diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index 08cc6b6bde5..00a8051aa47 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -27,8 +27,10 @@ _ocdav: double check the webdav property parsing when custom namespaces are used - [apiWebdavProperties1/setFileProperties.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L64) #### [Cannot set custom webDav properties](https://github.com/owncloud/product/issues/264) -- [apiWebdavProperties2/getFileProperties.feature:254](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L254) -- [apiWebdavProperties2/getFileProperties.feature:274](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L274) +- [apiWebdavProperties2/getFileProperties.feature:348](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L348) +- [apiWebdavProperties2/getFileProperties.feature:353](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L353) +- [apiWebdavProperties2/getFileProperties.feature:389](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L389) +- [apiWebdavProperties2/getFileProperties.feature:394](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L394) ### Sync Synchronization features like etag propagation, setting mtime and locking files @@ -564,8 +566,8 @@ Scenario Outline: Retrieving folder quota when quota is set and a file was recei #### [Private link support](https://github.com/owncloud/product/issues/201) #### [oc:privatelink property not returned in webdav responses](https://github.com/owncloud/product/issues/262) -- [apiWebdavProperties2/getFileProperties.feature:232](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L232) -- [apiWebdavProperties2/getFileProperties.feature:233](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L233) +- [apiWebdavProperties2/getFileProperties.feature:295](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L295) +- [apiWebdavProperties2/getFileProperties.feature:296](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L296) #### [changing user quota gives ocs status 103 / Cannot set quota](https://github.com/owncloud/product/issues/247) _requires a [CS3 user provisioning api that can update the quota for a user](https://github.com/cs3org/cs3apis/pull/95#issuecomment-772780683)_ @@ -1234,12 +1236,12 @@ _ocs: api compatibility, return correct status code_ - [apiShareOperationsToShares2/shareAccessByID.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L163) #### [[OC-storage] share-types field empty for shared file folder in webdav response](https://github.com/owncloud/ocis/issues/2144) -- [apiWebdavProperties2/getFileProperties.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L156) -- [apiWebdavProperties2/getFileProperties.feature:157](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L157) +- [apiWebdavProperties2/getFileProperties.feature:207](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L207) +- [apiWebdavProperties2/getFileProperties.feature:208](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L208) #### [Creating a public link with all permissions(31) fails](https://github.com/owncloud/ocis/issues/2145) -- [apiWebdavProperties2/getFileProperties.feature:206](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L206) -- [apiWebdavProperties2/getFileProperties.feature:207](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L207) +- [apiWebdavProperties2/getFileProperties.feature:265](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L265) +- [apiWebdavProperties2/getFileProperties.feature:266](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L266) #### [Cannot move folder/file from one received share to another](https://github.com/owncloud/ocis/issues/2442) - [apiShareUpdateToShares/updateShare.feature:241](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L241) @@ -1310,5 +1312,10 @@ _ocs: api compatibility, return correct status code_ - [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L59) - [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L60) +#### [moveShareInsideAnotherShare behaves differently on oCIS than oC10](https://github.com/owncloud/ocis/issues/3047) +- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L25) +- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L86) +- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L100) + Note: always have an empty line at the end of this file. The bash script that processes this file may not process a scenario reference on the last line. diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index 76d77eea0ed..70fdb231021 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -42,8 +42,10 @@ _ocdav: double check the webdav property parsing when custom namespaces are used - [apiWebdavProperties1/setFileProperties.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L64) #### [Cannot set custom webDav properties](https://github.com/owncloud/product/issues/264) -- [apiWebdavProperties2/getFileProperties.feature:254](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L254) -- [apiWebdavProperties2/getFileProperties.feature:274](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L274) +- [apiWebdavProperties2/getFileProperties.feature:348](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L348) +- [apiWebdavProperties2/getFileProperties.feature:353](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L353) +- [apiWebdavProperties2/getFileProperties.feature:389](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L389) +- [apiWebdavProperties2/getFileProperties.feature:394](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L394) ### Sync Synchronization features like etag propagation, setting mtime and locking files @@ -553,8 +555,8 @@ Scenario Outline: Retrieving folder quota when quota is set and a file was recei #### [Private link support](https://github.com/owncloud/product/issues/201) #### [oc:privatelink property not returned in webdav responses](https://github.com/owncloud/product/issues/262) -- [apiWebdavProperties2/getFileProperties.feature:232](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L232) -- [apiWebdavProperties2/getFileProperties.feature:233](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L233) +- [apiWebdavProperties2/getFileProperties.feature:295](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L295) +- [apiWebdavProperties2/getFileProperties.feature:296](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L296) #### [changing user quota gives ocs status 103 / Cannot set quota](https://github.com/owncloud/product/issues/247) _requires a [CS3 user provisioning api that can update the quota for a user](https://github.com/cs3org/cs3apis/pull/95#issuecomment-772780683)_ @@ -1252,12 +1254,12 @@ _ocs: api compatibility, return correct status code_ - [apiShareOperationsToShares2/shareAccessByID.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L163) #### [[OC-storage] share-types field empty for shared file folder in webdav response](https://github.com/owncloud/ocis/issues/2144) -- [apiWebdavProperties2/getFileProperties.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L156) -- [apiWebdavProperties2/getFileProperties.feature:157](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L157) +- [apiWebdavProperties2/getFileProperties.feature:207](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L207) +- [apiWebdavProperties2/getFileProperties.feature:208](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L208) #### [Creating a public link with all permissions(31) fails](https://github.com/owncloud/ocis/issues/2145) -- [apiWebdavProperties2/getFileProperties.feature:206](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L206) -- [apiWebdavProperties2/getFileProperties.feature:207](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L207) +- [apiWebdavProperties2/getFileProperties.feature:265](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L265) +- [apiWebdavProperties2/getFileProperties.feature:266](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L266) #### [Cannot move folder/file from one received share to another](https://github.com/owncloud/ocis/issues/2442) - [apiShareUpdateToShares/updateShare.feature:241](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L241) @@ -1312,5 +1314,10 @@ _ocs: api compatibility, return correct status code_ - [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L59) - [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L60) +#### [moveShareInsideAnotherShare behaves differently on oCIS than oC10](https://github.com/owncloud/ocis/issues/3047) +- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L25) +- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L86) +- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L100) + Note: always have an empty line at the end of this file. The bash script that processes this file may not process a scenario reference on the last line. From dbaebe64cdbf7c09a279857271fe33537ac8fa79 Mon Sep 17 00:00:00 2001 From: kobergj Date: Tue, 1 Feb 2022 11:04:33 +0100 Subject: [PATCH 27/49] [tests-only] Can't create folder names which are subset of "Shares" (#2484) * test fix with easy implementation Signed-off-by: jkoberg * use same logic for other above path also Signed-off-by: jkoberg * add special case handling to isSubPath Signed-off-by: jkoberg * refine isSubpath logic Signed-off-by: jkoberg * Stat on MKCOL before creating Signed-off-by: jkoberg * update comment to not include typos Signed-off-by: jkoberg * handle error correctly for MKCOL Signed-off-by: jkoberg --- .../http/services/owncloud/ocdav/mkcol.go | 22 +++++++++++++++++++ pkg/storage/registry/spaces/spaces.go | 18 +++++++++++++-- .../expected-failures-on-OCIS-storage.md | 11 +--------- .../expected-failures-on-S3NG-storage.md | 11 +--------- 4 files changed, 40 insertions(+), 22 deletions(-) diff --git a/internal/http/services/owncloud/ocdav/mkcol.go b/internal/http/services/owncloud/ocdav/mkcol.go index 72d57acb5f0..23d1b3fd59b 100644 --- a/internal/http/services/owncloud/ocdav/mkcol.go +++ b/internal/http/services/owncloud/ocdav/mkcol.go @@ -52,6 +52,28 @@ func (s *svc) handlePathMkcol(w http.ResponseWriter, r *http.Request, ns string) return } + // stat requested path to make sure it isn't existing yet + // NOTE: It could be on another storage provider than the 'parent' of it + sr, err := client.Stat(ctx, &provider.StatRequest{ + Ref: &provider.Reference{ + Path: fn, + }, + }) + if err != nil { + sublog.Error().Err(err).Str("path", fn).Msg("failed to look up storage space") + w.WriteHeader(http.StatusInternalServerError) + return + } + + if sr.Status.Code != rpc.Code_CODE_NOT_FOUND { + sublog.Info().Err(err).Str("path", fn).Interface("code", sr.Status.Code).Msg("response code for stat was unexpected") + // tests want this errorcode. StatusConflict would be more logical + w.WriteHeader(http.StatusMethodNotAllowed) + b, err := errors.Marshal(errors.SabredavMethodNotAllowed, "The resource you tried to create already exists", "") + errors.HandleWebdavError(&sublog, w, b, err) + return + } + parentPath := path.Dir(fn) space, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, client, parentPath) diff --git a/pkg/storage/registry/spaces/spaces.go b/pkg/storage/registry/spaces/spaces.go index c909abd2567..a1fa633597c 100644 --- a/pkg/storage/registry/spaces/spaces.go +++ b/pkg/storage/registry/spaces/spaces.go @@ -510,7 +510,7 @@ func (r *registry) findProvidersForAbsolutePathReference(ctx context.Context, pa deepestMountSpace = space deepestMountPathProvider = p - case strings.HasPrefix(spacePath, path) && !unique: + case !unique && isSubpath(spacePath, path): // and add all providers below and exactly matching the path // requested /foo, mountPath /foo/sub validSpaces = append(validSpaces, space) @@ -520,7 +520,7 @@ func (r *registry) findProvidersForAbsolutePathReference(ctx context.Context, pa deepestMountPathProvider = p } - case strings.HasPrefix(path, spacePath) && len(spacePath) > len(deepestMountPath): + case isSubpath(path, spacePath) && len(spacePath) > len(deepestMountPath): // eg. three providers: /foo, /foo/sub, /foo/sub/bar // requested /foo/sub/mob deepestMountPath = spacePath @@ -613,3 +613,17 @@ func (r *registry) findStorageSpaceOnProvider(ctx context.Context, addr string, } return res.StorageSpaces, nil } + +// isSubpath determines if `p` is a subpath of `path` +func isSubpath(p string, path string) bool { + if p == path { + return true + } + + r, err := filepath.Rel(path, p) + if err != nil { + return false + } + + return r != ".." && !strings.HasPrefix(r, "../") +} diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index 00a8051aa47..c53c1fe6080 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -1302,16 +1302,6 @@ _ocs: api compatibility, return correct status code_ ### [send PUT requests to another user's webDav endpoints as normal user](https://github.com/owncloud/ocis/issues/2893) - [apiAuthWebDav/webDavPUTAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPUTAuth.feature#L58) -#### [Creating a new folder which is a substring of Shares leads to Unknown Error](https://github.com/owncloud/ocis/issues/3033) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L27) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L29) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L30) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L32) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L45) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L48) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L59) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L60) - #### [moveShareInsideAnotherShare behaves differently on oCIS than oC10](https://github.com/owncloud/ocis/issues/3047) - [apiShareManagementToShares/moveShareInsideAnotherShare.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L25) - [apiShareManagementToShares/moveShareInsideAnotherShare.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L86) @@ -1319,3 +1309,4 @@ _ocs: api compatibility, return correct status code_ Note: always have an empty line at the end of this file. The bash script that processes this file may not process a scenario reference on the last line. + diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index 70fdb231021..e117a717577 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -1304,16 +1304,6 @@ _ocs: api compatibility, return correct status code_ ### [send PUT requests to another user's webDav endpoints as normal user](https://github.com/owncloud/ocis/issues/2893) - [apiAuthWebDav/webDavPUTAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPUTAuth.feature#L58) -#### [Creating a new folder which is a substring of Shares leads to Unknown Error](https://github.com/owncloud/ocis/issues/3033) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L27) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L29) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L30) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L32) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L45) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L48) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L59) -- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L60) - #### [moveShareInsideAnotherShare behaves differently on oCIS than oC10](https://github.com/owncloud/ocis/issues/3047) - [apiShareManagementToShares/moveShareInsideAnotherShare.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L25) - [apiShareManagementToShares/moveShareInsideAnotherShare.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L86) @@ -1321,3 +1311,4 @@ _ocs: api compatibility, return correct status code_ Note: always have an empty line at the end of this file. The bash script that processes this file may not process a scenario reference on the last line. + From d323f75218b3be15b03ca6c8b61cf80f5deb5c3d Mon Sep 17 00:00:00 2001 From: Willy Kloucek <34452982+wkloucek@users.noreply.github.com> Date: Tue, 1 Feb 2022 21:11:17 +0100 Subject: [PATCH 28/49] [edge] remove the ownCloud storage driver (#2495) * remove owncloud storage driver * remove owncloud storage driver integration tests and demo config files --- .../change-remove-owncloud-storage-driver.md | 10 + examples/oc-phoenix/storage-home.toml | 44 - examples/oc-phoenix/storage-oc.toml | 34 - pkg/storage/fs/loader/loader.go | 1 - pkg/storage/fs/owncloud/owncloud.go | 2462 ----------------- pkg/storage/fs/owncloud/owncloud_unix.go | 84 - pkg/storage/fs/owncloud/owncloud_windows.go | 76 - pkg/storage/fs/owncloud/upload.go | 556 ---- .../fixtures/storageprovider-owncloud.toml | 12 - .../integration/grpc/storageprovider_test.go | 26 +- 10 files changed, 13 insertions(+), 3292 deletions(-) create mode 100644 changelog/unreleased/change-remove-owncloud-storage-driver.md delete mode 100644 examples/oc-phoenix/storage-home.toml delete mode 100644 examples/oc-phoenix/storage-oc.toml delete mode 100644 pkg/storage/fs/owncloud/owncloud.go delete mode 100755 pkg/storage/fs/owncloud/owncloud_unix.go delete mode 100644 pkg/storage/fs/owncloud/owncloud_windows.go delete mode 100644 pkg/storage/fs/owncloud/upload.go delete mode 100644 tests/integration/grpc/fixtures/storageprovider-owncloud.toml diff --git a/changelog/unreleased/change-remove-owncloud-storage-driver.md b/changelog/unreleased/change-remove-owncloud-storage-driver.md new file mode 100644 index 00000000000..ac54b5733f2 --- /dev/null +++ b/changelog/unreleased/change-remove-owncloud-storage-driver.md @@ -0,0 +1,10 @@ +Change: remove the ownCloud storage driver + +We've removed the ownCloud storage driver because it was no longer +maintained after the ownCloud SQL storage driver was added. + +If you have been using the ownCloud storage driver, please switch +to the ownCloud SQL storage driver which brings you more features and +is under active maintenance. + +https://github.com/cs3org/reva/pull/2495 diff --git a/examples/oc-phoenix/storage-home.toml b/examples/oc-phoenix/storage-home.toml deleted file mode 100644 index 3ed223f4122..00000000000 --- a/examples/oc-phoenix/storage-home.toml +++ /dev/null @@ -1,44 +0,0 @@ -# This storage-home.toml config file will start a reva service that: -[shared] -jwt_secret = "Pive-Fumkiu4" -gatewaysvc = "localhost:19000" - -# - authenticates grpc storage provider requests using the internal jwt token -# - authenticates http upload and download requests requests using basic auth -# - serves the home storage provider on grpc port 12000 -# - serves http dataprovider for this storage on port 12001 -# - /data - dataprovider: file up and download -# -# The home storage will inject the username into the path and jail users into -# their home directory - -[grpc] -address = "0.0.0.0:12000" - -# This is a storage provider that grants direct access to the wrapped storage -# TODO same storage id as the /oc/ storage provider -# if we have an id, we can directly go to that storage, no need to wrap paths -# we have a locally running dataprovider -# this is where clients can find it -# the context path wrapper reads tho username from the context and prefixes the relative storage path with it -[grpc.services.storageprovider] -driver = "owncloud" -expose_data_server = true -data_server_url = "http://localhost:12001/data" -enable_home_creation = true - -[grpc.services.storageprovider.drivers.owncloud] -datadirectory = "/var/tmp/reva/data" -enable_home = true - - -[http] -address = "0.0.0.0:12001" - -[http.services.dataprovider] -driver = "owncloud" -temp_folder = "/var/tmp/reva/tmp" - -[http.services.dataprovider.drivers.owncloud] -datadirectory = "/var/tmp/reva/data" -enable_home = true diff --git a/examples/oc-phoenix/storage-oc.toml b/examples/oc-phoenix/storage-oc.toml deleted file mode 100644 index 5aac69f6721..00000000000 --- a/examples/oc-phoenix/storage-oc.toml +++ /dev/null @@ -1,34 +0,0 @@ -# This storage.toml config file will start a reva service that: -[shared] -jwt_secret = "Pive-Fumkiu4" -gatewaysvc = "localhost:19000" - -# - authenticates grpc storage provider requests using the internal jwt token -# - authenticates http upload and download requests requests using basic auth -# - serves the storage provider on grpc port 11000 -# - serves http dataprovider for this storage on port 11001 -# - /data - dataprovider: file up and download -[grpc] -address = "0.0.0.0:11000" - -# This is a storage provider that grants direct access to the wrapped storage -# we have a locally running dataprovider -[grpc.services.storageprovider] -driver = "owncloud" -expose_data_server = true -data_server_url = "http://localhost:11001/data" - -[grpc.services.storageprovider.drivers.owncloud] -datadirectory = "/var/tmp/reva/data" -redis = "redis:6379" -userprovidersvc = "localhost:18000" - -[http] -address = "0.0.0.0:11001" - -[http.services.dataprovider] -driver = "owncloud" -temp_folder = "/var/tmp/reva/tmp" - -[http.services.dataprovider.drivers.owncloud] -datadirectory = "/var/tmp/reva/data" diff --git a/pkg/storage/fs/loader/loader.go b/pkg/storage/fs/loader/loader.go index cd88c5ddc79..977c27532fd 100644 --- a/pkg/storage/fs/loader/loader.go +++ b/pkg/storage/fs/loader/loader.go @@ -29,7 +29,6 @@ import ( _ "github.com/cs3org/reva/pkg/storage/fs/localhome" _ "github.com/cs3org/reva/pkg/storage/fs/nextcloud" _ "github.com/cs3org/reva/pkg/storage/fs/ocis" - _ "github.com/cs3org/reva/pkg/storage/fs/owncloud" _ "github.com/cs3org/reva/pkg/storage/fs/owncloudsql" _ "github.com/cs3org/reva/pkg/storage/fs/s3" _ "github.com/cs3org/reva/pkg/storage/fs/s3ng" diff --git a/pkg/storage/fs/owncloud/owncloud.go b/pkg/storage/fs/owncloud/owncloud.go deleted file mode 100644 index 82ee727b85d..00000000000 --- a/pkg/storage/fs/owncloud/owncloud.go +++ /dev/null @@ -1,2462 +0,0 @@ -// Copyright 2018-2021 CERN -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package owncloud - -import ( - "context" - "encoding/hex" - "fmt" - "io" - "io/ioutil" - "net/url" - "os" - "path/filepath" - "strconv" - "strings" - "syscall" - "time" - - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" - "github.com/cs3org/reva/internal/grpc/services/storageprovider" - "github.com/cs3org/reva/pkg/appctx" - ctxpkg "github.com/cs3org/reva/pkg/ctx" - "github.com/cs3org/reva/pkg/errtypes" - "github.com/cs3org/reva/pkg/logger" - "github.com/cs3org/reva/pkg/mime" - "github.com/cs3org/reva/pkg/rgrpc/todo/pool" - "github.com/cs3org/reva/pkg/sharedconf" - "github.com/cs3org/reva/pkg/storage" - "github.com/cs3org/reva/pkg/storage/fs/registry" - "github.com/cs3org/reva/pkg/storage/utils/ace" - "github.com/cs3org/reva/pkg/storage/utils/chunking" - "github.com/cs3org/reva/pkg/storage/utils/templates" - "github.com/gomodule/redigo/redis" - "github.com/google/uuid" - "github.com/mitchellh/mapstructure" - "github.com/pkg/errors" - "github.com/pkg/xattr" -) - -const ( - // Currently,extended file attributes have four separated - // namespaces (user, trusted, security and system) followed by a dot. - // A non root user can only manipulate the user. namespace, which is what - // we will use to store ownCloud specific metadata. To prevent name - // collisions with other apps We are going to introduce a sub namespace - // "user.oc." - ocPrefix string = "user.oc." - - // idAttribute is the name of the filesystem extended attribute that is used to store the uuid in - idAttribute string = ocPrefix + "id" - - // SharePrefix is the prefix for sharing related extended attributes - sharePrefix string = ocPrefix + "grant." // grants are similar to acls, but they are not propagated down the tree when being changed - trashOriginPrefix string = ocPrefix + "o" - mdPrefix string = ocPrefix + "md." // arbitrary metadata - favPrefix string = ocPrefix + "fav." // favorite flag, per user - etagPrefix string = ocPrefix + "etag." // allow overriding a calculated etag with one from the extended attributes - checksumPrefix string = ocPrefix + "cs." - checksumsKey string = "http://owncloud.org/ns/checksums" - favoriteKey string = "http://owncloud.org/ns/favorite" - - spaceTypeAny = "*" -) - -var defaultPermissions *provider.ResourcePermissions = &provider.ResourcePermissions{ - // no permissions -} -var ownerPermissions *provider.ResourcePermissions = &provider.ResourcePermissions{ - // all permissions - AddGrant: true, - CreateContainer: true, - Delete: true, - GetPath: true, - GetQuota: true, - InitiateFileDownload: true, - InitiateFileUpload: true, - ListContainer: true, - ListFileVersions: true, - ListGrants: true, - ListRecycle: true, - Move: true, - PurgeRecycle: true, - RemoveGrant: true, - RestoreFileVersion: true, - RestoreRecycleItem: true, - Stat: true, - UpdateGrant: true, -} - -func init() { - registry.Register("owncloud", New) -} - -type config struct { - DataDirectory string `mapstructure:"datadirectory"` - UploadInfoDir string `mapstructure:"upload_info_dir"` - DeprecatedShareDirectory string `mapstructure:"sharedirectory"` - ShareFolder string `mapstructure:"share_folder"` - UserLayout string `mapstructure:"user_layout"` - Redis string `mapstructure:"redis"` - EnableHome bool `mapstructure:"enable_home"` - Scan bool `mapstructure:"scan"` - UserProviderEndpoint string `mapstructure:"userprovidersvc"` -} - -func parseConfig(m map[string]interface{}) (*config, error) { - c := &config{} - if err := mapstructure.Decode(m, c); err != nil { - err = errors.Wrap(err, "error decoding conf") - return nil, err - } - return c, nil -} - -func (c *config) init(m map[string]interface{}) { - if c.Redis == "" { - c.Redis = ":6379" - } - if c.UserLayout == "" { - c.UserLayout = "{{.Id.OpaqueId}}" - } - if c.UploadInfoDir == "" { - c.UploadInfoDir = "/var/tmp/reva/uploadinfo" - } - // fallback for old config - if c.DeprecatedShareDirectory != "" { - c.ShareFolder = c.DeprecatedShareDirectory - } - if c.ShareFolder == "" { - c.ShareFolder = "/Shares" - } - // ensure share folder always starts with slash - c.ShareFolder = filepath.Join("/", c.ShareFolder) - - // default to scanning if not configured - if _, ok := m["scan"]; !ok { - c.Scan = true - } - c.UserProviderEndpoint = sharedconf.GetGatewaySVC(c.UserProviderEndpoint) -} - -// New returns an implementation to of the storage.FS interface that talk to -// a local filesystem. -func New(m map[string]interface{}) (storage.FS, error) { - c, err := parseConfig(m) - if err != nil { - return nil, err - } - c.init(m) - - // c.DataDirectory should never end in / unless it is the root? - c.DataDirectory = filepath.Clean(c.DataDirectory) - - // create datadir if it does not exist - err = os.MkdirAll(c.DataDirectory, 0700) - if err != nil { - logger.New().Error().Err(err). - Str("path", c.DataDirectory). - Msg("could not create datadir") - } - - err = os.MkdirAll(c.UploadInfoDir, 0700) - if err != nil { - logger.New().Error().Err(err). - Str("path", c.UploadInfoDir). - Msg("could not create uploadinfo dir") - } - - pool := &redis.Pool{ - - MaxIdle: 3, - IdleTimeout: 240 * time.Second, - - Dial: func() (redis.Conn, error) { - c, err := redis.Dial("tcp", c.Redis) - if err != nil { - return nil, err - } - return c, err - }, - - TestOnBorrow: func(c redis.Conn, t time.Time) error { - _, err := c.Do("PING") - return err - }, - } - - return &ocfs{ - c: c, - pool: pool, - chunkHandler: chunking.NewChunkHandler(c.UploadInfoDir), - }, nil -} - -type ocfs struct { - c *config - pool *redis.Pool - chunkHandler *chunking.ChunkHandler -} - -func (fs *ocfs) Shutdown(ctx context.Context) error { - return fs.pool.Close() -} - -// scan files and add uuid to path mapping to kv store -func (fs *ocfs) scanFiles(ctx context.Context, conn redis.Conn) { - if fs.c.Scan { - fs.c.Scan = false // TODO ... in progress use mutex ? - log := appctx.GetLogger(ctx) - log.Debug().Str("path", fs.c.DataDirectory).Msg("scanning data directory") - err := filepath.Walk(fs.c.DataDirectory, func(path string, info os.FileInfo, err error) error { - if err != nil { - log.Error().Str("path", path).Err(err).Msg("error accessing path") - return filepath.SkipDir - } - // TODO(jfd) skip versions folder only if direct in users home dir - // we need to skip versions, otherwise a lookup by id might resolve to a version - if strings.Contains(path, "files_versions") { - log.Debug().Str("path", path).Err(err).Msg("skipping versions") - return filepath.SkipDir - } - - // reuse connection to store file ids - id := readOrCreateID(context.Background(), path, nil) - _, err = conn.Do("SET", id, path) - if err != nil { - log.Error().Str("path", path).Err(err).Msg("error caching id") - // continue scanning - return nil - } - - log.Debug().Str("path", path).Str("id", id).Msg("scanned path") - return nil - }) - if err != nil { - log.Error().Err(err).Str("path", fs.c.DataDirectory).Msg("error scanning data directory") - } - } -} - -// owncloud stores files in the files subfolder -// the incoming path starts with /, so we need to insert the files subfolder into the path -// and prefix the data directory -// TODO the path handed to a storage provider should not contain the username -func (fs *ocfs) toInternalPath(ctx context.Context, sp string) (ip string) { - if fs.c.EnableHome { - u := ctxpkg.ContextMustGetUser(ctx) - layout := templates.WithUser(u, fs.c.UserLayout) - // The inner filepath.Join prevents the path from breaking out of - // //files/ - ip = filepath.Join(fs.c.DataDirectory, layout, "files", filepath.Join("/", sp)) - } else { - // trim all / - sp = strings.Trim(sp, "/") - // p = "" or - // p = or - // p = /foo/bar.txt - segments := strings.SplitN(sp, "/", 2) - - if len(segments) == 1 && segments[0] == "" { - ip = fs.c.DataDirectory - return - } - - // parts[0] contains the username or userid. - u, err := fs.getUser(ctx, segments[0]) - if err != nil { - // TODO return invalid internal path? - return - } - layout := templates.WithUser(u, fs.c.UserLayout) - - if len(segments) == 1 { - // parts = "" - ip = filepath.Join(fs.c.DataDirectory, layout, "files") - } else { - // parts = "", "foo/bar.txt" - ip = filepath.Join(fs.c.DataDirectory, layout, "files", filepath.Join(segments[1])) - } - - } - return -} - -func (fs *ocfs) toInternalShadowPath(ctx context.Context, sp string) (internal string) { - if fs.c.EnableHome { - u := ctxpkg.ContextMustGetUser(ctx) - layout := templates.WithUser(u, fs.c.UserLayout) - internal = filepath.Join(fs.c.DataDirectory, layout, "shadow_files", sp) - } else { - // trim all / - sp = strings.Trim(sp, "/") - // p = "" or - // p = or - // p = /foo/bar.txt - segments := strings.SplitN(sp, "/", 2) - - if len(segments) == 1 && segments[0] == "" { - internal = fs.c.DataDirectory - return - } - - // parts[0] contains the username or userid. - u, err := fs.getUser(ctx, segments[0]) - if err != nil { - // TODO return invalid internal path? - return - } - layout := templates.WithUser(u, fs.c.UserLayout) - - if len(segments) == 1 { - // parts = "" - internal = filepath.Join(fs.c.DataDirectory, layout, "shadow_files") - } else { - // parts = "", "foo/bar.txt" - internal = filepath.Join(fs.c.DataDirectory, layout, "shadow_files", segments[1]) - } - } - return -} - -// ownloud stores versions in the files_versions subfolder -// the incoming path starts with /, so we need to insert the files subfolder into the path -// and prefix the data directory -// TODO the path handed to a storage provider should not contain the username -func (fs *ocfs) getVersionsPath(ctx context.Context, ip string) string { - // ip = /path/to/data//files/foo/bar.txt - // remove data dir - if fs.c.DataDirectory != "/" { - // fs.c.DataDirectory is a clean path, so it never ends in / - ip = strings.TrimPrefix(ip, fs.c.DataDirectory) - } - // ip = //files/foo/bar.txt - parts := strings.SplitN(ip, "/", 4) - - // parts[1] contains the username or userid. - u, err := fs.getUser(ctx, parts[1]) - if err != nil { - // TODO return invalid internal path? - return "" - } - layout := templates.WithUser(u, fs.c.UserLayout) - - switch len(parts) { - case 3: - // parts = "", "" - return filepath.Join(fs.c.DataDirectory, layout, "files_versions") - case 4: - // parts = "", "", "foo/bar.txt" - return filepath.Join(fs.c.DataDirectory, layout, "files_versions", filepath.Join("/", parts[3])) - default: - return "" // TODO Must not happen? - } - -} - -// owncloud stores trashed items in the files_trashbin subfolder of a users home -func (fs *ocfs) getRecyclePath(ctx context.Context) (string, error) { - u, ok := ctxpkg.ContextGetUser(ctx) - if !ok { - err := errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx") - return "", err - } - layout := templates.WithUser(u, fs.c.UserLayout) - return filepath.Join(fs.c.DataDirectory, layout, "files_trashbin/files"), nil -} - -func (fs *ocfs) getVersionRecyclePath(ctx context.Context) (string, error) { - u, ok := ctxpkg.ContextGetUser(ctx) - if !ok { - err := errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx") - return "", err - } - layout := templates.WithUser(u, fs.c.UserLayout) - return filepath.Join(fs.c.DataDirectory, layout, "files_trashbin/files_versions"), nil -} - -func (fs *ocfs) toStoragePath(ctx context.Context, ip string) (sp string) { - if fs.c.EnableHome { - u := ctxpkg.ContextMustGetUser(ctx) - layout := templates.WithUser(u, fs.c.UserLayout) - trim := filepath.Join(fs.c.DataDirectory, layout, "files") - sp = strings.TrimPrefix(ip, trim) - // root directory - if sp == "" { - sp = "/" - } - } else { - // ip = /data//files/foo/bar.txt - // remove data dir - if fs.c.DataDirectory != "/" { - // fs.c.DataDirectory is a clean path, so it never ends in / - ip = strings.TrimPrefix(ip, fs.c.DataDirectory) - // ip = //files/foo/bar.txt - } - - segments := strings.SplitN(ip, "/", 4) - // parts = "", "", "files", "foo/bar.txt" - switch len(segments) { - case 1: - sp = "/" - case 2: - sp = filepath.Join("/", segments[1]) - case 3: - sp = filepath.Join("/", segments[1]) - default: - sp = filepath.Join("/", segments[1], segments[3]) - } - } - log := appctx.GetLogger(ctx) - log.Debug().Str("driver", "ocfs").Str("ipath", ip).Str("spath", sp).Msg("toStoragePath") - return -} - -func (fs *ocfs) toStorageShadowPath(ctx context.Context, ip string) (sp string) { - if fs.c.EnableHome { - u := ctxpkg.ContextMustGetUser(ctx) - layout := templates.WithUser(u, fs.c.UserLayout) - trim := filepath.Join(fs.c.DataDirectory, layout, "shadow_files") - sp = strings.TrimPrefix(ip, trim) - } else { - // ip = /data//shadow_files/foo/bar.txt - // remove data dir - if fs.c.DataDirectory != "/" { - // fs.c.DataDirectory is a clean path, so it never ends in / - ip = strings.TrimPrefix(ip, fs.c.DataDirectory) - // ip = //shadow_files/foo/bar.txt - } - - segments := strings.SplitN(ip, "/", 4) - // parts = "", "", "shadow_files", "foo/bar.txt" - switch len(segments) { - case 1: - sp = "/" - case 2: - sp = filepath.Join("/", segments[1]) - case 3: - sp = filepath.Join("/", segments[1]) - default: - sp = filepath.Join("/", segments[1], segments[3]) - } - } - appctx.GetLogger(ctx).Debug().Str("driver", "ocfs").Str("ipath", ip).Str("spath", sp).Msg("toStorageShadowPath") - return -} - -// TODO the owner needs to come from a different place -func (fs *ocfs) getOwner(ip string) string { - ip = strings.TrimPrefix(ip, fs.c.DataDirectory) - parts := strings.SplitN(ip, "/", 3) - if len(parts) > 1 { - return parts[1] - } - return "" -} - -// TODO cache user lookup -func (fs *ocfs) getUser(ctx context.Context, usernameOrID string) (id *userpb.User, err error) { - u := ctxpkg.ContextMustGetUser(ctx) - // check if username matches and id is set - if u.Username == usernameOrID && u.Id != nil && u.Id.OpaqueId != "" { - return u, nil - } - // check if userid matches and username is set - if u.Id != nil && u.Id.OpaqueId == usernameOrID && u.Username != "" { - return u, nil - } - // look up at the userprovider - - // parts[0] contains the username or userid. use user service to look up id - c, err := pool.GetUserProviderServiceClient(fs.c.UserProviderEndpoint) - if err != nil { - appctx.GetLogger(ctx). - Error().Err(err). - Str("userprovidersvc", fs.c.UserProviderEndpoint). - Str("usernameOrID", usernameOrID). - Msg("could not get user provider client") - return nil, err - } - res, err := c.GetUser(ctx, &userpb.GetUserRequest{ - UserId: &userpb.UserId{OpaqueId: usernameOrID}, - }) - if err != nil { - appctx.GetLogger(ctx). - Error().Err(err). - Str("userprovidersvc", fs.c.UserProviderEndpoint). - Str("usernameOrID", usernameOrID). - Msg("could not get user") - return nil, err - } - - if res.Status.Code == rpc.Code_CODE_NOT_FOUND { - appctx.GetLogger(ctx). - Error(). - Str("userprovidersvc", fs.c.UserProviderEndpoint). - Str("usernameOrID", usernameOrID). - Interface("status", res.Status). - Msg("user not found") - return nil, fmt.Errorf("user not found") - } - - if res.Status.Code != rpc.Code_CODE_OK { - appctx.GetLogger(ctx). - Error(). - Str("userprovidersvc", fs.c.UserProviderEndpoint). - Str("usernameOrID", usernameOrID). - Interface("status", res.Status). - Msg("user lookup failed") - return nil, fmt.Errorf("user lookup failed") - } - return res.User, nil -} - -// permissionSet returns the permission set for the current user -func (fs *ocfs) permissionSet(ctx context.Context, owner *userpb.UserId) *provider.ResourcePermissions { - if owner == nil { - return &provider.ResourcePermissions{ - Stat: true, - } - } - u, ok := ctxpkg.ContextGetUser(ctx) - if !ok { - return &provider.ResourcePermissions{ - // no permissions - } - } - if u.Id == nil { - return &provider.ResourcePermissions{ - // no permissions - } - } - if u.Id.OpaqueId == owner.OpaqueId && u.Id.Idp == owner.Idp { - return &provider.ResourcePermissions{ - // owner has all permissions - AddGrant: true, - CreateContainer: true, - Delete: true, - GetPath: true, - GetQuota: true, - InitiateFileDownload: true, - InitiateFileUpload: true, - ListContainer: true, - ListFileVersions: true, - ListGrants: true, - ListRecycle: true, - Move: true, - PurgeRecycle: true, - RemoveGrant: true, - RestoreFileVersion: true, - RestoreRecycleItem: true, - Stat: true, - UpdateGrant: true, - } - } - // TODO fix permissions for share recipients by traversing reading acls up to the root? cache acls for the parent node and reuse it - return &provider.ResourcePermissions{ - AddGrant: true, - CreateContainer: true, - Delete: true, - GetPath: true, - GetQuota: true, - InitiateFileDownload: true, - InitiateFileUpload: true, - ListContainer: true, - ListFileVersions: true, - ListGrants: true, - ListRecycle: true, - Move: true, - PurgeRecycle: true, - RemoveGrant: true, - RestoreFileVersion: true, - RestoreRecycleItem: true, - Stat: true, - UpdateGrant: true, - } -} -func (fs *ocfs) convertToResourceInfo(ctx context.Context, fi os.FileInfo, ip string, sp string, c redis.Conn, mdKeys []string) *provider.ResourceInfo { - id := readOrCreateID(ctx, ip, c) - - etag := calcEtag(ctx, fi) - - if val, err := xattr.Get(ip, etagPrefix+etag); err == nil { - appctx.GetLogger(ctx).Debug(). - Str("ipath", ip). - Str("calcetag", etag). - Str("etag", string(val)). - Msg("overriding calculated etag") - etag = string(val) - } - - mdKeysMap := make(map[string]struct{}) - for _, k := range mdKeys { - mdKeysMap[k] = struct{}{} - } - - var returnAllKeys bool - if _, ok := mdKeysMap["*"]; len(mdKeys) == 0 || ok { - returnAllKeys = true - } - - metadata := map[string]string{} - - if _, ok := mdKeysMap[favoriteKey]; returnAllKeys || ok { - favorite := "" - if u, ok := ctxpkg.ContextGetUser(ctx); ok { - // the favorite flag is specific to the user, so we need to incorporate the userid - if uid := u.GetId(); uid != nil { - fa := fmt.Sprintf("%s%s@%s", favPrefix, uid.GetOpaqueId(), uid.GetIdp()) - if val, err := xattr.Get(ip, fa); err == nil { - appctx.GetLogger(ctx).Debug(). - Str("ipath", ip). - Str("favorite", string(val)). - Str("username", u.GetUsername()). - Msg("found favorite flag") - favorite = string(val) - } - } else { - appctx.GetLogger(ctx).Error().Err(errtypes.UserRequired("userrequired")).Msg("user has no id") - } - } else { - appctx.GetLogger(ctx).Error().Err(errtypes.UserRequired("userrequired")).Msg("error getting user from ctx") - } - metadata[favoriteKey] = favorite - } - - list, err := xattr.List(ip) - if err == nil { - for _, entry := range list { - // filter out non-custom properties - if !strings.HasPrefix(entry, mdPrefix) { - continue - } - if val, err := xattr.Get(ip, entry); err == nil { - k := entry[len(mdPrefix):] - if _, ok := mdKeysMap[k]; returnAllKeys || ok { - metadata[k] = string(val) - } - } else { - appctx.GetLogger(ctx).Error().Err(err). - Str("entry", entry). - Msg("error retrieving xattr metadata") - } - } - } else { - appctx.GetLogger(ctx).Error().Err(err).Msg("error getting list of extended attributes") - } - - ri := &provider.ResourceInfo{ - Id: &provider.ResourceId{OpaqueId: id}, - Path: sp, - Type: getResourceType(fi.IsDir()), - Etag: etag, - MimeType: mime.Detect(fi.IsDir(), ip), - Size: uint64(fi.Size()), - Mtime: &types.Timestamp{ - Seconds: uint64(fi.ModTime().Unix()), - // TODO read nanos from where? Nanos: fi.MTimeNanos, - }, - ArbitraryMetadata: &provider.ArbitraryMetadata{ - Metadata: metadata, - }, - } - - if owner, err := fs.getUser(ctx, fs.getOwner(ip)); err == nil { - ri.Owner = owner.Id - } else { - appctx.GetLogger(ctx).Error().Err(err).Msg("error getting owner") - } - - ri.PermissionSet = fs.permissionSet(ctx, ri.Owner) - - // checksums - if !fi.IsDir() { - if _, checksumRequested := mdKeysMap[checksumsKey]; returnAllKeys || checksumRequested { - // TODO which checksum was requested? sha1 adler32 or md5? for now hardcode sha1? - readChecksumIntoResourceChecksum(ctx, ip, storageprovider.XSSHA1, ri) - readChecksumIntoOpaque(ctx, ip, storageprovider.XSMD5, ri) - readChecksumIntoOpaque(ctx, ip, storageprovider.XSAdler32, ri) - } - } - - return ri -} -func getResourceType(isDir bool) provider.ResourceType { - if isDir { - return provider.ResourceType_RESOURCE_TYPE_CONTAINER - } - return provider.ResourceType_RESOURCE_TYPE_FILE -} - -// CreateStorageSpace creates a storage space -func (fs *ocfs) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) { - if req.Type != "personal" { - return nil, errtypes.NotSupported("only personal spaces supported") - } - - layout := templates.WithUser(req.Owner, fs.c.UserLayout) - - homePaths := []string{ - filepath.Join(fs.c.DataDirectory, layout, "files"), - filepath.Join(fs.c.DataDirectory, layout, "files_trashbin"), - filepath.Join(fs.c.DataDirectory, layout, "files_versions"), - filepath.Join(fs.c.DataDirectory, layout, "uploads"), - filepath.Join(fs.c.DataDirectory, layout, "shadow_files"), - } - - for _, v := range homePaths { - if err := os.MkdirAll(v, 0700); err != nil { - return nil, errors.Wrap(err, "ocfs: error creating home path: "+v) - } - } - - return &provider.CreateStorageSpaceResponse{ - Status: &rpc.Status{Code: rpc.Code_CODE_OK}, - }, nil -} - -func readOrCreateID(ctx context.Context, ip string, conn redis.Conn) string { - log := appctx.GetLogger(ctx) - - // read extended file attribute for id - // generate if not present - var id []byte - var err error - if id, err = xattr.Get(ip, idAttribute); err != nil { - log.Warn().Err(err).Str("driver", "owncloud").Str("ipath", ip).Msg("error reading file id") - - uuid := uuid.New() - // store uuid - id = uuid[:] - if err := xattr.Set(ip, idAttribute, id); err != nil { - log.Error().Err(err).Str("driver", "owncloud").Str("ipath", ip).Msg("error storing file id") - } - // TODO cache path for uuid in redis - // TODO reuse conn? - if conn != nil { - _, err := conn.Do("SET", uuid.String(), ip) - if err != nil { - log.Error().Err(err).Str("driver", "owncloud").Str("ipath", ip).Msg("error caching id") - // continue - } - } - } - // todo sign metadata - var uid uuid.UUID - if uid, err = uuid.FromBytes(id); err != nil { - log.Error().Err(err).Msg("error parsing uuid") - return "" - } - return uid.String() -} - -func (fs *ocfs) getPath(ctx context.Context, id *provider.ResourceId) (string, error) { - log := appctx.GetLogger(ctx) - c := fs.pool.Get() - defer c.Close() - fs.scanFiles(ctx, c) - ip, err := redis.String(c.Do("GET", id.OpaqueId)) - if err != nil { - return "", errtypes.NotFound(id.OpaqueId) - } - - idFromXattr, err := xattr.Get(ip, idAttribute) - if err != nil { - return "", errtypes.NotFound(id.OpaqueId) - } - - uid, err := uuid.FromBytes(idFromXattr) - if err != nil { - log.Error().Err(err).Msg("error parsing uuid") - } - - if uid.String() != id.OpaqueId { - if _, err := c.Do("DEL", id.OpaqueId); err != nil { - return "", err - } - return "", errtypes.NotFound(id.OpaqueId) - } - - return ip, nil -} - -// GetPathByID returns the storage relative path for the file id, without the internal namespace -func (fs *ocfs) GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error) { - ip, err := fs.getPath(ctx, id) - if err != nil { - return "", err - } - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.GetPath { - return "", errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return "", errtypes.NotFound(fs.toStoragePath(ctx, ip)) - } - return "", errors.Wrap(err, "ocfs: error reading permissions") - } - - return fs.toStoragePath(ctx, ip), nil -} - -// resolve takes in a request path or request id and converts it to an internal path. -func (fs *ocfs) resolve(ctx context.Context, ref *provider.Reference) (string, error) { - - // if storage id is set look up that - if ref.ResourceId != nil { - ip, err := fs.getPath(ctx, ref.ResourceId) - if err != nil { - return "", err - } - return filepath.Join("/", ip, filepath.Join("/", ref.Path)), nil - } - - // use a path - return fs.toInternalPath(ctx, ref.Path), nil - -} - -func (fs *ocfs) DenyGrant(ctx context.Context, ref *provider.Reference, g *provider.Grantee) error { - return errtypes.NotSupported("ocfs: deny grant not supported") -} - -func (fs *ocfs) AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error { - ip, err := fs.resolve(ctx, ref) - if err != nil { - return errors.Wrap(err, "ocfs: error resolving reference") - } - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.AddGrant { - return errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return errtypes.NotFound(fs.toStoragePath(ctx, ip)) - } - return errors.Wrap(err, "ocfs: error reading permissions") - } - - e := ace.FromGrant(g) - principal, value := e.Marshal() - if err := xattr.Set(ip, sharePrefix+principal, value); err != nil { - return err - } - return fs.propagate(ctx, ip) -} - -// extractACEsFromAttrs reads ACEs in the list of attrs from the file -func extractACEsFromAttrs(ctx context.Context, ip string, attrs []string) (entries []*ace.ACE) { - log := appctx.GetLogger(ctx) - entries = []*ace.ACE{} - for i := range attrs { - if strings.HasPrefix(attrs[i], sharePrefix) { - var value []byte - var err error - if value, err = xattr.Get(ip, attrs[i]); err != nil { - log.Error().Err(err).Str("attr", attrs[i]).Msg("could not read attribute") - continue - } - var e *ace.ACE - principal := attrs[i][len(sharePrefix):] - if e, err = ace.Unmarshal(principal, value); err != nil { - log.Error().Err(err).Str("principal", principal).Str("attr", attrs[i]).Msg("could not unmarshal ace") - continue - } - entries = append(entries, e) - } - } - return -} - -// TODO if user is owner but no acls found he can do everything? -// The owncloud driver does not integrate with the os so, for now, the owner can do everything, see ownerPermissions. -// Should this change we can store an acl for the owner in every node. -// We could also add default acls that can only the admin can set, eg for a read only storage? -// Someone needs to write to provide the content that should be read only, so this would likely be an acl for a group anyway. -// We need the storage relative path so we can calculate the permissions -// for the node based on all acls in the tree up to the root -func (fs *ocfs) readPermissions(ctx context.Context, ip string) (p *provider.ResourcePermissions, err error) { - - u, ok := ctxpkg.ContextGetUser(ctx) - if !ok { - appctx.GetLogger(ctx).Debug().Str("ipath", ip).Msg("no user in context, returning default permissions") - return defaultPermissions, nil - } - // check if the current user is the owner - if fs.getOwner(ip) == u.Id.OpaqueId { - appctx.GetLogger(ctx).Debug().Str("ipath", ip).Msg("user is owner, returning owner permissions") - return ownerPermissions, nil - } - - // for non owners this is a little more complicated: - aggregatedPermissions := &provider.ResourcePermissions{} - // add default permissions - addPermissions(aggregatedPermissions, defaultPermissions) - - // determine root - rp := fs.toInternalPath(ctx, "") - // TODO rp will be the datadir ... be we don't want to go up that high. The users home is far enough - np := ip - - if ip == rp { - return &provider.ResourcePermissions{ - // grant read access to the root - GetPath: true, - GetQuota: true, - ListContainer: true, - ListFileVersions: true, - ListGrants: true, - ListRecycle: true, - Stat: true, - }, nil - } - - // for an efficient group lookup convert the list of groups to a map - // groups are just strings ... groupnames ... or group ids ??? AAARGH !!! - groupsMap := make(map[string]bool, len(u.Groups)) - for i := range u.Groups { - groupsMap[u.Groups[i]] = true - } - - var e *ace.ACE - // for all segments, starting at the leaf - for np != rp { - - var attrs []string - if attrs, err = xattr.List(np); err != nil { - appctx.GetLogger(ctx).Error().Err(err).Str("ipath", np).Msg("error listing attributes") - return nil, err - } - - userace := sharePrefix + "u:" + u.Id.OpaqueId - userFound := false - for i := range attrs { - // we only need the find the user once per node - switch { - case !userFound && attrs[i] == userace: - e, err = fs.readACE(ctx, np, "u:"+u.Id.OpaqueId) - case strings.HasPrefix(attrs[i], sharePrefix+"g:"): - g := strings.TrimPrefix(attrs[i], sharePrefix+"g:") - if groupsMap[g] { - e, err = fs.readACE(ctx, np, "g:"+g) - } else { - // no need to check attribute - continue - } - default: - // no need to check attribute - continue - } - - switch { - case err == nil: - addPermissions(aggregatedPermissions, e.Grant().GetPermissions()) - appctx.GetLogger(ctx).Debug().Str("ipath", np).Str("principal", strings.TrimPrefix(attrs[i], sharePrefix)).Interface("permissions", aggregatedPermissions).Msg("adding permissions") - case isNoData(err): - err = nil - appctx.GetLogger(ctx).Error().Str("ipath", np).Str("principal", strings.TrimPrefix(attrs[i], sharePrefix)).Interface("attrs", attrs).Msg("no permissions found on node, but they were listed") - default: - appctx.GetLogger(ctx).Error().Err(err).Str("ipath", np).Str("principal", strings.TrimPrefix(attrs[i], sharePrefix)).Msg("error reading permissions") - return nil, err - } - } - - np = filepath.Dir(np) - } - - // 3. read user permissions until one is found? - // what if, when checking /a/b/c/d, /a/b has write permission, but /a/b/c has not? - // those are two shares one read only, and a higher one rw, - // should the higher one be used? - // or, since we did find a matching ace in a lower node use that because it matches the principal? - // this would allow ai user to share a folder rm but take away the write capability for eg a docs folder inside it. - // 4. read group permissions until all groups of the user are matched? - // same as for user permission, but we need to keep going further up the tree until all groups of the user were matched. - // what if a user has thousands of groups? - // we will always have to walk to the root. - // but the same problem occurs for a user with 2 groups but where only one group was used to share. - // in any case we need to iterate the aces, not the number of groups of the user. - // listing the aces can be used to match the principals, we do not need to fully real all aces - // what if, when checking /a/b/c/d, /a/b has write permission for group g, but /a/b/c has an ace for another group h the user is also a member of? - // it would allow restricting a users permissions by resharing something with him with lower permission? - // so if you have reshare permissions you could accidentially restrict users access to a subfolder of a rw share to ro by sharing it to another group as ro when they are part of both groups - // it makes more sense to have explicit negative permissions - - // TODO we need to read all parents ... until we find a matching ace? - appctx.GetLogger(ctx).Debug().Interface("permissions", aggregatedPermissions).Str("ipath", ip).Msg("returning aggregated permissions") - return aggregatedPermissions, nil -} - -func isNoData(err error) bool { - if xerr, ok := err.(*xattr.Error); ok { - if serr, ok2 := xerr.Err.(syscall.Errno); ok2 { - return serr == syscall.ENODATA - } - } - return false -} - -// The os not exists error is buried inside the xattr error, -// so we cannot just use os.IsNotExists(). -func isNotFound(err error) bool { - if xerr, ok := err.(*xattr.Error); ok { - if serr, ok2 := xerr.Err.(syscall.Errno); ok2 { - return serr == syscall.ENOENT - } - } - return false -} - -func (fs *ocfs) readACE(ctx context.Context, ip string, principal string) (e *ace.ACE, err error) { - var b []byte - if b, err = xattr.Get(ip, sharePrefix+principal); err != nil { - return nil, err - } - if e, err = ace.Unmarshal(principal, b); err != nil { - return nil, err - } - return -} - -// additive merging of permissions only -func addPermissions(p1 *provider.ResourcePermissions, p2 *provider.ResourcePermissions) { - p1.AddGrant = p1.AddGrant || p2.AddGrant - p1.CreateContainer = p1.CreateContainer || p2.CreateContainer - p1.Delete = p1.Delete || p2.Delete - p1.GetPath = p1.GetPath || p2.GetPath - p1.GetQuota = p1.GetQuota || p2.GetQuota - p1.InitiateFileDownload = p1.InitiateFileDownload || p2.InitiateFileDownload - p1.InitiateFileUpload = p1.InitiateFileUpload || p2.InitiateFileUpload - p1.ListContainer = p1.ListContainer || p2.ListContainer - p1.ListFileVersions = p1.ListFileVersions || p2.ListFileVersions - p1.ListGrants = p1.ListGrants || p2.ListGrants - p1.ListRecycle = p1.ListRecycle || p2.ListRecycle - p1.Move = p1.Move || p2.Move - p1.PurgeRecycle = p1.PurgeRecycle || p2.PurgeRecycle - p1.RemoveGrant = p1.RemoveGrant || p2.RemoveGrant - p1.RestoreFileVersion = p1.RestoreFileVersion || p2.RestoreFileVersion - p1.RestoreRecycleItem = p1.RestoreRecycleItem || p2.RestoreRecycleItem - p1.Stat = p1.Stat || p2.Stat - p1.UpdateGrant = p1.UpdateGrant || p2.UpdateGrant -} - -func (fs *ocfs) ListGrants(ctx context.Context, ref *provider.Reference) (grants []*provider.Grant, err error) { - log := appctx.GetLogger(ctx) - var ip string - if ip, err = fs.resolve(ctx, ref); err != nil { - return nil, errors.Wrap(err, "ocfs: error resolving reference") - } - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.ListGrants { - return nil, errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return nil, errtypes.NotFound(fs.toStoragePath(ctx, ip)) - } - return nil, errors.Wrap(err, "ocfs: error reading permissions") - } - - var attrs []string - if attrs, err = xattr.List(ip); err != nil { - // TODO err might be a not exists - log.Error().Err(err).Msg("error listing attributes") - return nil, err - } - - log.Debug().Interface("attrs", attrs).Msg("read attributes") - - aces := extractACEsFromAttrs(ctx, ip, attrs) - - grants = make([]*provider.Grant, 0, len(aces)) - for i := range aces { - grants = append(grants, aces[i].Grant()) - } - - return grants, nil -} - -func (fs *ocfs) RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) (err error) { - - var ip string - if ip, err = fs.resolve(ctx, ref); err != nil { - return errors.Wrap(err, "ocfs: error resolving reference") - } - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.ListContainer { - return errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return errtypes.NotFound(fs.toStoragePath(ctx, ip)) - } - return errors.Wrap(err, "ocfs: error reading permissions") - } - - var attr string - if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { - attr = sharePrefix + "g:" + g.Grantee.GetGroupId().OpaqueId - } else { - attr = sharePrefix + "u:" + g.Grantee.GetUserId().OpaqueId - } - - if err = xattr.Remove(ip, attr); err != nil { - return - } - - return fs.propagate(ctx, ip) -} - -func (fs *ocfs) UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error { - ip, err := fs.resolve(ctx, ref) - if err != nil { - return errors.Wrap(err, "ocfs: error resolving reference") - } - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.UpdateGrant { - return errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return errtypes.NotFound(fs.toStoragePath(ctx, ip)) - } - return errors.Wrap(err, "ocfs: error reading permissions") - } - - e := ace.FromGrant(g) - principal, value := e.Marshal() - if err := xattr.Set(ip, sharePrefix+principal, value); err != nil { - return err - } - return fs.propagate(ctx, ip) -} - -func (fs *ocfs) CreateHome(ctx context.Context) error { - return errtypes.NotSupported("use CreateStorageSpace with type personal") - /* - u, ok := ctxpkg.ContextGetUser(ctx) - if !ok { - err := errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx") - return err - } - layout := templates.WithUser(u, fs.c.UserLayout) - - homePaths := []string{ - filepath.Join(fs.c.DataDirectory, layout, "files"), - filepath.Join(fs.c.DataDirectory, layout, "files_trashbin"), - filepath.Join(fs.c.DataDirectory, layout, "files_versions"), - filepath.Join(fs.c.DataDirectory, layout, "uploads"), - filepath.Join(fs.c.DataDirectory, layout, "shadow_files"), - } - - for _, v := range homePaths { - if err := os.MkdirAll(v, 0700); err != nil { - return errors.Wrap(err, "ocfs: error creating home path: "+v) - } - } - - return nil - */ -} - -// If home is enabled, the relative home is always the empty string -func (fs *ocfs) GetHome(ctx context.Context) (string, error) { - return "", errtypes.NotSupported("use CreateStorageSpace with type personal") - /* - if !fs.c.EnableHome { - return "", errtypes.NotSupported("ocfs: get home not supported") - } - return "", nil - */ -} - -func (fs *ocfs) CreateDir(ctx context.Context, ref *provider.Reference) (err error) { - - ip, err := fs.resolve(ctx, ref) - if err != nil { - return err - } - - // check permissions of parent dir - if perm, err := fs.readPermissions(ctx, filepath.Dir(ip)); err == nil { - if !perm.CreateContainer { - return errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return errtypes.NotFound(ref.Path) - } - return errors.Wrap(err, "ocfs: error reading permissions") - } - - if err = os.Mkdir(ip, 0700); err != nil { - if os.IsNotExist(err) { - return errtypes.NotFound(ref.Path) - } - // FIXME we also need already exists error, webdav expects 405 MethodNotAllowed - return errors.Wrap(err, "ocfs: error creating dir "+ref.Path) - } - return fs.propagate(ctx, ip) -} - -// TouchFile as defined in the storage.FS interface -func (fs *ocfs) TouchFile(ctx context.Context, ref *provider.Reference) error { - return fmt.Errorf("unimplemented: TouchFile") -} - -func (fs *ocfs) isShareFolderChild(sp string) bool { - return strings.HasPrefix(sp, fs.c.ShareFolder) -} - -func (fs *ocfs) isShareFolderRoot(sp string) bool { - return sp == fs.c.ShareFolder -} - -func (fs *ocfs) CreateReference(ctx context.Context, sp string, targetURI *url.URL) error { - if !fs.isShareFolderChild(sp) { - return errtypes.PermissionDenied("ocfs: cannot create references outside the share folder: share_folder=" + "/Shares" + " path=" + sp) - } - - ip := fs.toInternalShadowPath(ctx, sp) - // TODO check permission? - - dir, _ := filepath.Split(ip) - if err := os.MkdirAll(dir, 0700); err != nil { - return errors.Wrapf(err, "ocfs: error creating shadow path %s", dir) - } - - f, err := os.Create(ip) - if err != nil { - return errors.Wrapf(err, "ocfs: error creating shadow file %s", ip) - } - - err = xattr.FSet(f, mdPrefix+"target", []byte(targetURI.String())) - if err != nil { - return errors.Wrapf(err, "ocfs: error setting the target %s on the shadow file %s", targetURI.String(), ip) - } - return nil -} - -func (fs *ocfs) setMtime(ctx context.Context, ip string, mtime string) error { - log := appctx.GetLogger(ctx) - if mt, err := parseMTime(mtime); err == nil { - // updating mtime also updates atime - if err := os.Chtimes(ip, mt, mt); err != nil { - log.Error().Err(err). - Str("ipath", ip). - Time("mtime", mt). - Msg("could not set mtime") - return errors.Wrap(err, "could not set mtime") - } - } else { - log.Error().Err(err). - Str("ipath", ip). - Str("mtime", mtime). - Msg("could not parse mtime") - return errors.Wrap(err, "could not parse mtime") - } - return nil -} -func (fs *ocfs) SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) (err error) { - log := appctx.GetLogger(ctx) - - var ip string - if ip, err = fs.resolve(ctx, ref); err != nil { - return errors.Wrap(err, "ocfs: error resolving reference") - } - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.InitiateFileUpload { // TODO add dedicated permission? - return errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) - } - return errors.Wrap(err, "ocfs: error reading permissions") - } - - var fi os.FileInfo - fi, err = os.Stat(ip) - if err != nil { - if os.IsNotExist(err) { - return errtypes.NotFound(fs.toStoragePath(ctx, ip)) - } - return errors.Wrap(err, "ocfs: error stating "+ip) - } - - errs := []error{} - - if md.Metadata != nil { - if val, ok := md.Metadata["mtime"]; ok { - err := fs.setMtime(ctx, ip, val) - if err != nil { - errs = append(errs, errors.Wrap(err, "could not set mtime")) - } - // remove from metadata - delete(md.Metadata, "mtime") - } - // TODO(jfd) special handling for atime? - // TODO(jfd) allow setting birth time (btime)? - // TODO(jfd) any other metadata that is interesting? fileid? - if val, ok := md.Metadata["etag"]; ok { - etag := calcEtag(ctx, fi) - val = fmt.Sprintf("\"%s\"", strings.Trim(val, "\"")) - if etag == val { - log.Debug(). - Str("ipath", ip). - Str("etag", val). - Msg("ignoring request to update identical etag") - } else - // etag is only valid until the calculated etag changes - // TODO(jfd) cleanup in a batch job - if err := xattr.Set(ip, etagPrefix+etag, []byte(val)); err != nil { - log.Error().Err(err). - Str("ipath", ip). - Str("calcetag", etag). - Str("etag", val). - Msg("could not set etag") - errs = append(errs, errors.Wrap(err, "could not set etag")) - } - delete(md.Metadata, "etag") - } - if val, ok := md.Metadata["http://owncloud.org/ns/favorite"]; ok { - // TODO we should not mess with the user here ... the favorites is now a user specific property for a file - // that cannot be mapped to extended attributes without leaking who has marked a file as a favorite - // it is a specific case of a tag, which is user individual as well - // TODO there are different types of tags - // 1. public that are managed by everyone - // 2. private tags that are only visible to the user - // 3. system tags that are only visible to the system - // 4. group tags that are only visible to a group ... - // urgh ... well this can be solved using different namespaces - // 1. public = p: - // 2. private = u:: for user specific - // 3. system = s: for system - // 4. group = g:: - // 5. app? = a:: for apps? - // obviously this only is secure when the u/s/g/a namespaces are not accessible by users in the filesystem - // public tags can be mapped to extended attributes - if u, ok := ctxpkg.ContextGetUser(ctx); ok { - // the favorite flag is specific to the user, so we need to incorporate the userid - if uid := u.GetId(); uid != nil { - fa := fmt.Sprintf("%s%s@%s", favPrefix, uid.GetOpaqueId(), uid.GetIdp()) - if err := xattr.Set(ip, fa, []byte(val)); err != nil { - log.Error().Err(err). - Str("ipath", ip). - Interface("user", u). - Str("key", fa). - Msg("could not set favorite flag") - errs = append(errs, errors.Wrap(err, "could not set favorite flag")) - } - } else { - log.Error(). - Str("ipath", ip). - Interface("user", u). - Msg("user has no id") - errs = append(errs, errors.Wrap(errtypes.UserRequired("userrequired"), "user has no id")) - } - } else { - log.Error(). - Str("ipath", ip). - Interface("user", u). - Msg("error getting user from ctx") - errs = append(errs, errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx")) - } - // remove from metadata - delete(md.Metadata, "http://owncloud.org/ns/favorite") - } - } - for k, v := range md.Metadata { - if err := xattr.Set(ip, mdPrefix+k, []byte(v)); err != nil { - log.Error().Err(err). - Str("ipath", ip). - Str("key", k). - Str("val", v). - Msg("could not set metadata") - errs = append(errs, errors.Wrap(err, "could not set metadata")) - } - } - switch len(errs) { - case 0: - return fs.propagate(ctx, ip) - case 1: - return errs[0] - default: - // TODO how to return multiple errors? - return errors.New("multiple errors occurred, see log for details") - } -} - -func parseMTime(v string) (t time.Time, err error) { - p := strings.SplitN(v, ".", 2) - var sec, nsec int64 - if sec, err = strconv.ParseInt(p[0], 10, 64); err == nil { - if len(p) > 1 { - nsec, err = strconv.ParseInt(p[1], 10, 64) - } - } - return time.Unix(sec, nsec), err -} - -func (fs *ocfs) UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) (err error) { - log := appctx.GetLogger(ctx) - - var ip string - if ip, err = fs.resolve(ctx, ref); err != nil { - return errors.Wrap(err, "ocfs: error resolving reference") - } - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.InitiateFileUpload { // TODO add dedicated permission? - return errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return errtypes.NotFound(fs.toStoragePath(ctx, ip)) - } - return errors.Wrap(err, "ocfs: error reading permissions") - } - - _, err = os.Stat(ip) - if err != nil { - if os.IsNotExist(err) { - return errtypes.NotFound(fs.toStoragePath(ctx, ip)) - } - return errors.Wrap(err, "ocfs: error stating "+ip) - } - - errs := []error{} - for _, k := range keys { - switch k { - case "http://owncloud.org/ns/favorite": - if u, ok := ctxpkg.ContextGetUser(ctx); ok { - // the favorite flag is specific to the user, so we need to incorporate the userid - if uid := u.GetId(); uid != nil { - fa := fmt.Sprintf("%s%s@%s", favPrefix, uid.GetOpaqueId(), uid.GetIdp()) - if err := xattr.Remove(ip, fa); err != nil { - log.Error().Err(err). - Str("ipath", ip). - Interface("user", u). - Str("key", fa). - Msg("could not unset favorite flag") - errs = append(errs, errors.Wrap(err, "could not unset favorite flag")) - } - } else { - log.Error(). - Str("ipath", ip). - Interface("user", u). - Msg("user has no id") - errs = append(errs, errors.Wrap(errtypes.UserRequired("userrequired"), "user has no id")) - } - } else { - log.Error(). - Str("ipath", ip). - Interface("user", u). - Msg("error getting user from ctx") - errs = append(errs, errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx")) - } - default: - if err = xattr.Remove(ip, mdPrefix+k); err != nil { - // a non-existing attribute will return an error, which we can ignore - // (using string compare because the error type is syscall.Errno and not wrapped/recognizable) - if e, ok := err.(*xattr.Error); !ok || !(e.Err.Error() == "no data available" || - // darwin - e.Err.Error() == "attribute not found") { - log.Error().Err(err). - Str("ipath", ip). - Str("key", k). - Msg("could not unset metadata") - errs = append(errs, errors.Wrap(err, "could not unset metadata")) - } - } - } - } - - switch len(errs) { - case 0: - return fs.propagate(ctx, ip) - case 1: - return errs[0] - default: - // TODO how to return multiple errors? - return errors.New("multiple errors occurred, see log for details") - } -} - -// GetLock returns an existing lock on the given reference -func (fs *ocfs) GetLock(ctx context.Context, ref *provider.Reference) (*provider.Lock, error) { - return nil, errtypes.NotSupported("unimplemented") -} - -// SetLock puts a lock on the given reference -func (fs *ocfs) SetLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { - return errtypes.NotSupported("unimplemented") -} - -// RefreshLock refreshes an existing lock on the given reference -func (fs *ocfs) RefreshLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { - return errtypes.NotSupported("unimplemented") -} - -// Unlock removes an existing lock from the given reference -func (fs *ocfs) Unlock(ctx context.Context, ref *provider.Reference) error { - return errtypes.NotSupported("unimplemented") -} - -// Delete is actually only a move to trash -// -// This is a first optimistic approach. -// When a file has versions and we want to delete the file it could happen that -// the service crashes before all moves are finished. -// That would result in invalid state like the main files was moved but the -// versions were not. -// We will live with that compromise since this storage driver will be -// deprecated soon. -func (fs *ocfs) Delete(ctx context.Context, ref *provider.Reference) (err error) { - var ip string - if ip, err = fs.resolve(ctx, ref); err != nil { - return errors.Wrap(err, "ocfs: error resolving reference") - } - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.Delete { - return errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) - } - return errors.Wrap(err, "ocfs: error reading permissions") - } - - _, err = os.Stat(ip) - if err != nil { - if os.IsNotExist(err) { - return errtypes.NotFound(fs.toStoragePath(ctx, ip)) - } - return errors.Wrap(err, "ocfs: error stating "+ip) - } - - rp, err := fs.getRecyclePath(ctx) - if err != nil { - return errors.Wrap(err, "ocfs: error resolving recycle path") - } - - if err := os.MkdirAll(rp, 0700); err != nil { - return errors.Wrap(err, "ocfs: error creating trashbin dir "+rp) - } - - // ip is the path on disk ... we need only the path relative to root - origin := filepath.Dir(fs.toStoragePath(ctx, ip)) - - err = fs.trash(ctx, ip, rp, origin) - if err != nil { - return errors.Wrapf(err, "ocfs: error deleting file %s", ip) - } - err = fs.trashVersions(ctx, ip, origin) - if err != nil { - return errors.Wrapf(err, "ocfs: error deleting versions of file %s", ip) - } - return nil -} - -func (fs *ocfs) trash(ctx context.Context, ip string, rp string, origin string) error { - // set origin location in metadata - if err := xattr.Set(ip, trashOriginPrefix, []byte(origin)); err != nil { - return err - } - - // move to trash location - dtime := time.Now().Unix() - tgt := filepath.Join(rp, fmt.Sprintf("%s.d%d", filepath.Base(ip), dtime)) - - // The condition reads: "if the file exists" - // I know this check is hard to read because of the double negation - // but this way we avoid to duplicate the code following the if block. - // If two deletes happen fast consecutively they will have the same `dtime`, - // therefore we have to increase the 'dtime' to avoid collisions. - if _, err := os.Stat(tgt); !errors.Is(err, os.ErrNotExist) { - // timestamp collision, try again with higher value: - dtime++ - tgt = filepath.Join(rp, fmt.Sprintf("%s.d%d", filepath.Base(ip), dtime)) - } - if err := os.Rename(ip, tgt); err != nil { - return errors.Wrap(err, "ocfs: could not move item to trash") - } - - return fs.propagate(ctx, filepath.Dir(ip)) -} - -func (fs *ocfs) trashVersions(ctx context.Context, ip string, origin string) error { - vp := fs.getVersionsPath(ctx, ip) - vrp, err := fs.getVersionRecyclePath(ctx) - if err != nil { - return errors.Wrap(err, "error resolving versions recycle path") - } - - if err := os.MkdirAll(vrp, 0700); err != nil { - return errors.Wrap(err, "ocfs: error creating trashbin dir "+vrp) - } - - // Ignore error since the only possible error is malformed pattern. - versions, _ := filepath.Glob(vp + ".v*") - for _, v := range versions { - err := fs.trash(ctx, v, vrp, origin) - if err != nil { - return errors.Wrap(err, "ocfs: error deleting file "+v) - } - } - return nil -} - -func (fs *ocfs) Move(ctx context.Context, oldRef, newRef *provider.Reference) (err error) { - var oldIP string - if oldIP, err = fs.resolve(ctx, oldRef); err != nil { - return errors.Wrap(err, "ocfs: error resolving reference") - } - - // check permissions - if perm, err := fs.readPermissions(ctx, oldIP); err == nil { - if !perm.Move { // TODO add dedicated permission? - return errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(oldIP))) - } - return errors.Wrap(err, "ocfs: error reading permissions") - } - - var newIP string - if newIP, err = fs.resolve(ctx, newRef); err != nil { - return errors.Wrap(err, "ocfs: error resolving reference") - } - - // TODO check target permissions ... if it exists - - if err = os.Rename(oldIP, newIP); err != nil { - return errors.Wrap(err, "ocfs: error moving "+oldIP+" to "+newIP) - } - - log := appctx.GetLogger(ctx) - conn := fs.pool.Get() - defer conn.Close() - // Ideally if we encounter an error here we should rollback the Move/Rename. - // But since the owncloud storage driver is not being actively used by anyone other - // than the acceptance tests we should be fine by ignoring the errors. - _ = filepath.Walk(newIP, func(path string, info os.FileInfo, err error) error { - if err != nil { - // TODO(c0rby): rollback the move in case of an error - log.Error().Str("path", path).Err(err).Msg("error caching id") - return nil - } - id := readOrCreateID(context.Background(), path, nil) - _, err = conn.Do("SET", id, path) - if err != nil { - // TODO(c0rby): rollback the move in case of an error - log.Error().Str("path", path).Err(err).Msg("error caching id") - } - return nil - }) - if err := fs.propagate(ctx, newIP); err != nil { - return err - } - if err := fs.propagate(ctx, filepath.Dir(oldIP)); err != nil { - return err - } - return nil -} - -func (fs *ocfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []string) (*provider.ResourceInfo, error) { - ip, err := fs.resolve(ctx, ref) - if err != nil { - // TODO return correct errtype - if _, ok := err.(errtypes.IsNotFound); ok { - return nil, err - } - return nil, errors.Wrap(err, "ocfs: error resolving reference") - } - p := fs.toStoragePath(ctx, ip) - - if fs.c.EnableHome { - if fs.isShareFolderChild(p) { - return fs.getMDShareFolder(ctx, p, mdKeys) - } - } - - // If GetMD is called for a path shared with the user then the path is - // already wrapped. (fs.resolve wraps the path) - if strings.HasPrefix(p, fs.c.DataDirectory) { - ip = p - } - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.Stat { - return nil, errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) - } - return nil, errors.Wrap(err, "ocfs: error reading permissions") - } - - md, err := os.Stat(ip) - if err != nil { - if os.IsNotExist(err) { - return nil, errtypes.NotFound(fs.toStoragePath(ctx, ip)) - } - return nil, errors.Wrap(err, "ocfs: error stating "+ip) - } - c := fs.pool.Get() - defer c.Close() - m := fs.convertToResourceInfo(ctx, md, ip, fs.toStoragePath(ctx, ip), c, mdKeys) - - return m, nil -} - -func (fs *ocfs) getMDShareFolder(ctx context.Context, sp string, mdKeys []string) (*provider.ResourceInfo, error) { - ip := fs.toInternalShadowPath(ctx, sp) - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.Stat { - return nil, errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) - } - return nil, errors.Wrap(err, "ocfs: error reading permissions") - } - - md, err := os.Stat(ip) - if err != nil { - if os.IsNotExist(err) { - return nil, errtypes.NotFound(fs.toStorageShadowPath(ctx, ip)) - } - return nil, errors.Wrapf(err, "ocfs: error stating %s", ip) - } - c := fs.pool.Get() - defer c.Close() - m := fs.convertToResourceInfo(ctx, md, ip, fs.toStorageShadowPath(ctx, ip), c, mdKeys) - if !fs.isShareFolderRoot(sp) { - m.Type = provider.ResourceType_RESOURCE_TYPE_REFERENCE - ref, err := xattr.Get(ip, mdPrefix+"target") - if err != nil { - if isNotFound(err) { - return nil, errtypes.NotFound(fs.toStorageShadowPath(ctx, ip)) - } - return nil, err - } - m.Target = string(ref) - } - - return m, nil -} - -func (fs *ocfs) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys []string) ([]*provider.ResourceInfo, error) { - log := appctx.GetLogger(ctx) - - ip, err := fs.resolve(ctx, ref) - if err != nil { - return nil, errors.Wrap(err, "ocfs: error resolving reference") - } - sp := fs.toStoragePath(ctx, ip) - - if fs.c.EnableHome { - log.Debug().Msg("home enabled") - if strings.HasPrefix(sp, "/") { - // permissions checked in listWithHome - return fs.listWithHome(ctx, "/", sp, mdKeys) - } - } - - log.Debug().Msg("list with nominal home") - // permissions checked in listWithNominalHome - return fs.listWithNominalHome(ctx, sp, mdKeys) -} - -func (fs *ocfs) listWithNominalHome(ctx context.Context, ip string, mdKeys []string) ([]*provider.ResourceInfo, error) { - - // If a user wants to list a folder shared with him the path will already - // be wrapped with the files directory path of the share owner. - // In that case we don't want to wrap the path again. - if !strings.HasPrefix(ip, fs.c.DataDirectory) { - ip = fs.toInternalPath(ctx, ip) - } - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.ListContainer { - return nil, errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) - } - return nil, errors.Wrap(err, "ocfs: error reading permissions") - } - - mds, err := ioutil.ReadDir(ip) - if err != nil { - return nil, errors.Wrapf(err, "ocfs: error listing %s", ip) - } - c := fs.pool.Get() - defer c.Close() - finfos := []*provider.ResourceInfo{} - for _, md := range mds { - cp := filepath.Join(ip, md.Name()) - m := fs.convertToResourceInfo(ctx, md, cp, fs.toStoragePath(ctx, cp), c, mdKeys) - finfos = append(finfos, m) - } - return finfos, nil -} - -func (fs *ocfs) listWithHome(ctx context.Context, home, p string, mdKeys []string) ([]*provider.ResourceInfo, error) { - log := appctx.GetLogger(ctx) - if p == home { - log.Debug().Msg("listing home") - return fs.listHome(ctx, home, mdKeys) - } - - if fs.isShareFolderRoot(p) { - log.Debug().Msg("listing share folder root") - return fs.listShareFolderRoot(ctx, p, mdKeys) - } - - if fs.isShareFolderChild(p) { - return nil, errtypes.PermissionDenied("ocfs: error listing folders inside the shared folder, only file references are stored inside") - } - - log.Debug().Msg("listing nominal home") - return fs.listWithNominalHome(ctx, p, mdKeys) -} - -func (fs *ocfs) listHome(ctx context.Context, home string, mdKeys []string) ([]*provider.ResourceInfo, error) { - // list files - ip := fs.toInternalPath(ctx, home) - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.ListContainer { - return nil, errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) - } - return nil, errors.Wrap(err, "ocfs: error reading permissions") - } - - mds, err := ioutil.ReadDir(ip) - if err != nil { - return nil, errors.Wrap(err, "ocfs: error listing files") - } - - c := fs.pool.Get() - defer c.Close() - - finfos := []*provider.ResourceInfo{} - for _, md := range mds { - cp := filepath.Join(ip, md.Name()) - m := fs.convertToResourceInfo(ctx, md, cp, fs.toStoragePath(ctx, cp), c, mdKeys) - finfos = append(finfos, m) - } - - // list shadow_files - ip = fs.toInternalShadowPath(ctx, home) - mds, err = ioutil.ReadDir(ip) - if err != nil { - return nil, errors.Wrap(err, "ocfs: error listing shadow_files") - } - for _, md := range mds { - cp := filepath.Join(ip, md.Name()) - m := fs.convertToResourceInfo(ctx, md, cp, fs.toStorageShadowPath(ctx, cp), c, mdKeys) - finfos = append(finfos, m) - } - return finfos, nil -} - -func (fs *ocfs) listShareFolderRoot(ctx context.Context, sp string, mdKeys []string) ([]*provider.ResourceInfo, error) { - ip := fs.toInternalShadowPath(ctx, sp) - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.ListContainer { - return nil, errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) - } - return nil, errors.Wrap(err, "ocfs: error reading permissions") - } - - mds, err := ioutil.ReadDir(ip) - if err != nil { - if os.IsNotExist(err) { - return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) - } - return nil, errors.Wrap(err, "ocfs: error listing shadow_files") - } - - c := fs.pool.Get() - defer c.Close() - - finfos := []*provider.ResourceInfo{} - for _, md := range mds { - cp := filepath.Join(ip, md.Name()) - m := fs.convertToResourceInfo(ctx, md, cp, fs.toStorageShadowPath(ctx, cp), c, mdKeys) - m.Type = provider.ResourceType_RESOURCE_TYPE_REFERENCE - ref, err := xattr.Get(cp, mdPrefix+"target") - if err != nil { - return nil, err - } - m.Target = string(ref) - finfos = append(finfos, m) - } - - return finfos, nil -} - -func (fs *ocfs) archiveRevision(ctx context.Context, vbp string, ip string) error { - // move existing file to versions dir - vp := fmt.Sprintf("%s.v%d", vbp, time.Now().Unix()) - if err := os.MkdirAll(filepath.Dir(vp), 0700); err != nil { - return errors.Wrap(err, "ocfs: error creating versions dir "+vp) - } - - // TODO(jfd): make sure rename is atomic, missing fsync ... - if err := os.Rename(ip, vp); err != nil { - return errors.Wrap(err, "ocfs: error renaming from "+ip+" to "+vp) - } - - return nil -} - -func (fs *ocfs) copyMD(s string, t string) (err error) { - var attrs []string - if attrs, err = xattr.List(s); err != nil { - return err - } - for i := range attrs { - if strings.HasPrefix(attrs[i], ocPrefix) { - var d []byte - if d, err = xattr.Get(s, attrs[i]); err != nil { - return err - } - if err = xattr.Set(t, attrs[i], d); err != nil { - return err - } - } - } - return nil -} - -func (fs *ocfs) Download(ctx context.Context, ref *provider.Reference) (io.ReadCloser, error) { - ip, err := fs.resolve(ctx, ref) - if err != nil { - return nil, errors.Wrap(err, "ocfs: error resolving reference") - } - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.InitiateFileDownload { - return nil, errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) - } - return nil, errors.Wrap(err, "ocfs: error reading permissions") - } - - r, err := os.Open(ip) - if err != nil { - if os.IsNotExist(err) { - return nil, errtypes.NotFound(fs.toStoragePath(ctx, ip)) - } - return nil, errors.Wrap(err, "ocfs: error reading "+ip) - } - return r, nil -} - -func (fs *ocfs) ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error) { - ip, err := fs.resolve(ctx, ref) - if err != nil { - return nil, errors.Wrap(err, "ocfs: error resolving reference") - } - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.ListFileVersions { - return nil, errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) - } - return nil, errors.Wrap(err, "ocfs: error reading permissions") - } - - vp := fs.getVersionsPath(ctx, ip) - - bn := filepath.Base(ip) - - revisions := []*provider.FileVersion{} - mds, err := ioutil.ReadDir(filepath.Dir(vp)) - if err != nil { - return nil, errors.Wrap(err, "ocfs: error reading"+filepath.Dir(vp)) - } - for i := range mds { - rev := fs.filterAsRevision(ctx, bn, mds[i]) - if rev != nil { - revisions = append(revisions, rev) - } - - } - return revisions, nil -} - -func (fs *ocfs) filterAsRevision(ctx context.Context, bn string, md os.FileInfo) *provider.FileVersion { - if strings.HasPrefix(md.Name(), bn) { - // versions have filename.ext.v12345678 - version := md.Name()[len(bn)+2:] // truncate ".v" to get version mtime - mtime, err := strconv.Atoi(version) - if err != nil { - log := appctx.GetLogger(ctx) - log.Error().Err(err).Str("path", md.Name()).Msg("invalid version mtime") - return nil - } - // TODO(jfd) trashed versions are in the files_trashbin/versions folder ... not relevant here - return &provider.FileVersion{ - Key: version, - Size: uint64(md.Size()), - Mtime: uint64(mtime), - Etag: calcEtag(ctx, md), - } - } - return nil -} - -func (fs *ocfs) DownloadRevision(ctx context.Context, ref *provider.Reference, revisionKey string) (io.ReadCloser, error) { - return nil, errtypes.NotSupported("download revision") -} - -func (fs *ocfs) RestoreRevision(ctx context.Context, ref *provider.Reference, revisionKey string) error { - ip, err := fs.resolve(ctx, ref) - if err != nil { - return errors.Wrap(err, "ocfs: error resolving reference") - } - - // check permissions - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.RestoreFileVersion { - return errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) - } - return errors.Wrap(err, "ocfs: error reading permissions") - } - - vp := fs.getVersionsPath(ctx, ip) - rp := vp + ".v" + revisionKey - - // check revision exists - rs, err := os.Stat(rp) - if err != nil { - return err - } - - if !rs.Mode().IsRegular() { - return fmt.Errorf("%s is not a regular file", rp) - } - - source, err := os.Open(rp) - if err != nil { - return err - } - defer source.Close() - - // destination should be available, otherwise we could not have navigated to its revisions - if err := fs.archiveRevision(ctx, fs.getVersionsPath(ctx, ip), ip); err != nil { - return err - } - - destination, err := os.Create(ip) - if err != nil { - // TODO(jfd) bring back revision in case sth goes wrong? - return err - } - defer destination.Close() - - _, err = io.Copy(destination, source) - - if err != nil { - return err - } - - // TODO(jfd) bring back revision in case sth goes wrong? - return fs.propagate(ctx, ip) -} - -func (fs *ocfs) PurgeRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string) error { - rp, err := fs.getRecyclePath(ctx) - if err != nil { - return errors.Wrap(err, "ocfs: error resolving recycle path") - } - ip := filepath.Join(rp, filepath.Clean(key)) - // TODO check permission? - - // check permissions - /* are they stored in the trash? - if perm, err := fs.readPermissions(ctx, ip); err == nil { - if !perm.ListContainer { - return nil, errtypes.PermissionDenied("") - } - } else { - if isNotFound(err) { - return nil, errtypes.NotFound(fs.unwrap(ctx, filepath.Dir(ip))) - } - return nil, errors.Wrap(err, "ocfs: error reading permissions") - } - */ - - err = os.Remove(ip) - if err != nil { - return errors.Wrap(err, "ocfs: error deleting recycle item") - } - err = os.RemoveAll(filepath.Join(filepath.Dir(rp), "versions", filepath.Clean(key))) - if err != nil { - return errors.Wrap(err, "ocfs: error deleting recycle item versions") - } - // TODO delete keyfiles, keys, share-keys - return nil -} - -func (fs *ocfs) EmptyRecycle(ctx context.Context, ref *provider.Reference) error { - // TODO check permission? on what? user must be the owner - rp, err := fs.getRecyclePath(ctx) - if err != nil { - return errors.Wrap(err, "ocfs: error resolving recycle path") - } - err = os.RemoveAll(rp) - if err != nil { - return errors.Wrap(err, "ocfs: error deleting recycle files") - } - err = os.RemoveAll(filepath.Join(filepath.Dir(rp), "versions")) - if err != nil { - return errors.Wrap(err, "ocfs: error deleting recycle files versions") - } - // TODO delete keyfiles, keys, share-keys ... or just everything? - return nil -} - -func (fs *ocfs) convertToRecycleItem(ctx context.Context, rp string, md os.FileInfo) *provider.RecycleItem { - // trashbin items have filename.ext.d12345678 - suffix := filepath.Ext(md.Name()) - if len(suffix) == 0 || !strings.HasPrefix(suffix, ".d") { - log := appctx.GetLogger(ctx) - log.Error().Str("path", md.Name()).Msg("invalid trash item suffix") - return nil - } - trashtime := suffix[2:] // truncate "d" to get trashbin time - ttime, err := strconv.Atoi(trashtime) - if err != nil { - log := appctx.GetLogger(ctx) - log.Error().Err(err).Str("path", md.Name()).Msg("invalid trash time") - return nil - } - var v []byte - if v, err = xattr.Get(filepath.Join(rp, md.Name()), trashOriginPrefix); err != nil { - log := appctx.GetLogger(ctx) - log.Error().Err(err).Str("path", md.Name()).Msg("could not read origin") - return nil - } - // ownCloud 10 stores the parent dir of the deleted item as the location in the oc_files_trashbin table - // we use extended attributes for original location, but also only the parent location, which is why - // we need to join and trim the path when listing it - originalPath := filepath.Join(string(v), strings.TrimSuffix(filepath.Base(md.Name()), suffix)) - - return &provider.RecycleItem{ - Type: getResourceType(md.IsDir()), - Key: md.Name(), - // TODO do we need to prefix the path? it should be relative to this storage root, right? - Ref: &provider.Reference{ - Path: originalPath, - }, - Size: uint64(md.Size()), - DeletionTime: &types.Timestamp{ - Seconds: uint64(ttime), - // no nanos available - }, - } -} - -func (fs *ocfs) ListRecycle(ctx context.Context, ref *provider.Reference, key, relativePath string) ([]*provider.RecycleItem, error) { - // TODO check permission? on what? user must be the owner? - rp, err := fs.getRecyclePath(ctx) - if err != nil { - return nil, errors.Wrap(err, "ocfs: error resolving recycle path") - } - - // list files folder - mds, err := ioutil.ReadDir(filepath.Join(rp, key)) - if err != nil { - log := appctx.GetLogger(ctx) - log.Debug().Err(err).Str("path", rp).Msg("trash not readable") - // TODO jfd only ignore not found errors - return []*provider.RecycleItem{}, nil - } - // TODO (jfd) limit and offset - items := []*provider.RecycleItem{} - for i := range mds { - ri := fs.convertToRecycleItem(ctx, rp, mds[i]) - if ri != nil { - items = append(items, ri) - } - - } - return items, nil -} - -func (fs *ocfs) RestoreRecycleItem(ctx context.Context, ref *provider.Reference, key, relativePath string, restoreRef *provider.Reference) error { - // TODO check permission? on what? user must be the owner? - log := appctx.GetLogger(ctx) - rp, err := fs.getRecyclePath(ctx) - if err != nil { - return errors.Wrap(err, "ocfs: error resolving recycle path") - } - src := filepath.Join(rp, filepath.Clean(key)) - - suffix := filepath.Ext(src) - if len(suffix) == 0 || !strings.HasPrefix(suffix, ".d") { - log.Error().Str("key", key).Str("path", src).Msg("invalid trash item suffix") - return nil - } - - if restoreRef == nil { - restoreRef = &provider.Reference{} - } - if restoreRef.Path == "" { - v, err := xattr.Get(src, trashOriginPrefix) - if err != nil { - log.Error().Err(err).Str("key", key).Str("path", src).Msg("could not read origin") - } - restoreRef.Path = filepath.Join("/", filepath.Clean(string(v)), strings.TrimSuffix(filepath.Base(src), suffix)) - } - tgt := fs.toInternalPath(ctx, restoreRef.Path) - // move back to original location - if err := os.Rename(src, tgt); err != nil { - log.Error().Err(err).Str("key", key).Str("restorePath", restoreRef.Path).Str("src", src).Str("tgt", tgt).Msg("could not restore item") - return errors.Wrap(err, "ocfs: could not restore item") - } - // unset trash origin location in metadata - if err := xattr.Remove(tgt, trashOriginPrefix); err != nil { - // just a warning, will be overwritten next time it is deleted - log.Warn().Err(err).Str("key", key).Str("tgt", tgt).Msg("could not unset origin") - } - // TODO(jfd) restore versions - - return fs.propagate(ctx, tgt) -} - -func (fs *ocfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { - - var ( - spaceType = spaceTypeAny - // spaceID = spaceIDAny - // nodeID = spaceIDAny - err error - ) - - for i := range filter { - switch filter[i].Type { - case provider.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE: - spaceType = filter[i].GetSpaceType() - case provider.ListStorageSpacesRequest_Filter_TYPE_ID: - // spaceID, nodeID = utils.SplitStorageSpaceID(filter[i].GetId().OpaqueId) - } - } - - spaces := []*provider.StorageSpace{} - if spaceType != spaceTypeAny && spaceType != "personal" { - // owncloud only has personal spaces - // TODO implement external spaces? - return spaces, nil - } - - // all folders with a files folder could be a personal space - matches, err := filepath.Glob(filepath.Join(fs.c.DataDirectory, "*", "files")) - if err != nil { - return nil, err - } - - for i := range matches { - - id := readOrCreateID(context.Background(), matches[i], nil) - space := &provider.StorageSpace{ - Id: &provider.StorageSpaceId{OpaqueId: id}, - // Owner: , // TODO from path layout? - // Root: , //? - } - spaces = append(spaces, space) - } - - // FIXME: The linter doesn't like empty branches - // if len(matches) == 0 && nodeID != spaceID { - // TODO lookup by id - // } - - return spaces, nil -} - -// UpdateStorageSpace updates a storage space -func (fs *ocfs) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) { - return nil, errtypes.NotSupported("update storage space") -} - -// DeleteStorageSpace deletes a storage space -func (fs *ocfs) DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) error { - return errtypes.NotSupported("delete storage space") -} - -func (fs *ocfs) propagate(ctx context.Context, leafPath string) error { - var root string - if fs.c.EnableHome { - root = fs.toInternalPath(ctx, "/") - } else { - owner := fs.getOwner(leafPath) - root = fs.toInternalPath(ctx, owner) - } - if !strings.HasPrefix(leafPath, root) { - err := errors.New("internal path outside root") - appctx.GetLogger(ctx).Error(). - Err(err). - Str("leafPath", leafPath). - Str("root", root). - Msg("could not propagate change") - return err - } - - fi, err := os.Stat(leafPath) - if err != nil { - appctx.GetLogger(ctx).Error(). - Err(err). - Str("leafPath", leafPath). - Str("root", root). - Msg("could not propagate change") - return err - } - - parts := strings.Split(strings.TrimPrefix(leafPath, root), "/") - // root never ends in / so the split returns an empty first element, which we can skip - // we do not need to chmod the last element because it is the leaf path (< and not <= comparison) - for i := 1; i < len(parts); i++ { - appctx.GetLogger(ctx).Debug(). - Str("leafPath", leafPath). - Str("root", root). - Int("i", i). - Interface("parts", parts). - Msg("propagating change") - if err := os.Chtimes(filepath.Join(root), fi.ModTime(), fi.ModTime()); err != nil { - appctx.GetLogger(ctx).Error(). - Err(err). - Str("leafPath", leafPath). - Str("root", root). - Msg("could not propagate change") - return err - } - root = filepath.Join(root, parts[i]) - } - return nil -} - -func readChecksumIntoResourceChecksum(ctx context.Context, nodePath, algo string, ri *provider.ResourceInfo) { - v, err := xattr.Get(nodePath, checksumPrefix+algo) - log := appctx.GetLogger(ctx). - Debug(). - Err(err). - Str("nodepath", nodePath). - Str("algorithm", algo) - switch { - case err == nil: - ri.Checksum = &provider.ResourceChecksum{ - Type: storageprovider.PKG2GRPCXS(algo), - Sum: hex.EncodeToString(v), - } - case isNoData(err): - log.Msg("checksum not set") - case isNotFound(err): - log.Msg("file not found") - default: - log.Msg("could not read checksum") - } -} - -func readChecksumIntoOpaque(ctx context.Context, nodePath, algo string, ri *provider.ResourceInfo) { - v, err := xattr.Get(nodePath, checksumPrefix+algo) - log := appctx.GetLogger(ctx). - Debug(). - Err(err). - Str("nodepath", nodePath). - Str("algorithm", algo) - switch { - case err == nil: - if ri.Opaque == nil { - ri.Opaque = &types.Opaque{ - Map: map[string]*types.OpaqueEntry{}, - } - } - ri.Opaque.Map[algo] = &types.OpaqueEntry{ - Decoder: "plain", - Value: []byte(hex.EncodeToString(v)), - } - case isNoData(err): - log.Msg("checksum not set") - case isNotFound(err): - log.Msg("file not found") - default: - log.Msg("could not read checksum") - } -} - -// TODO propagate etag and mtime or append event to history? propagate on disk ... -// - but propagation is a separate task. only if upload was successful ... diff --git a/pkg/storage/fs/owncloud/owncloud_unix.go b/pkg/storage/fs/owncloud/owncloud_unix.go deleted file mode 100755 index 61cc65433df..00000000000 --- a/pkg/storage/fs/owncloud/owncloud_unix.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2018-2021 CERN -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -//go:build !windows -// +build !windows - -package owncloud - -import ( - "context" - "crypto/md5" - "encoding/binary" - "fmt" - "os" - "strings" - "syscall" - - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/pkg/appctx" -) - -// TODO(jfd) get rid of the differences between unix and windows. the inode and dev should never be used for the etag because it interferes with backups - -// calcEtag will create an etag based on the md5 of -// - mtime, -// - inode (if available), -// - device (if available) and -// - size. -// errors are logged, but an etag will still be returned -func calcEtag(ctx context.Context, fi os.FileInfo) string { - log := appctx.GetLogger(ctx) - h := md5.New() - err := binary.Write(h, binary.BigEndian, fi.ModTime().UnixNano()) - if err != nil { - log.Error().Err(err).Msg("error writing mtime") - } - stat, ok := fi.Sys().(*syscall.Stat_t) - if ok { - // take device and inode into account - err = binary.Write(h, binary.BigEndian, stat.Ino) - if err != nil { - log.Error().Err(err).Msg("error writing inode") - } - err = binary.Write(h, binary.BigEndian, stat.Dev) - if err != nil { - log.Error().Err(err).Msg("error writing device") - } - } - err = binary.Write(h, binary.BigEndian, fi.Size()) - if err != nil { - log.Error().Err(err).Msg("error writing size") - } - etag := fmt.Sprintf(`"%x"`, h.Sum(nil)) - return fmt.Sprintf("\"%s\"", strings.Trim(etag, "\"")) -} - -func (fs *ocfs) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, uint64, error) { - // TODO quota of which storage space? - // we could use the logged in user, but when a user has access to multiple storages this falls short - // for now return quota of root - stat := syscall.Statfs_t{} - err := syscall.Statfs(fs.toInternalPath(ctx, "/"), &stat) - if err != nil { - return 0, 0, err - } - total := stat.Blocks * uint64(stat.Bsize) // Total data blocks in filesystem - used := (stat.Blocks - stat.Bavail) * uint64(stat.Bsize) // Free blocks available to unprivileged user - return total, used, nil -} diff --git a/pkg/storage/fs/owncloud/owncloud_windows.go b/pkg/storage/fs/owncloud/owncloud_windows.go deleted file mode 100644 index 80e86e4cd4e..00000000000 --- a/pkg/storage/fs/owncloud/owncloud_windows.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2018-2021 CERN -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -//go:build windows -// +build windows - -package owncloud - -import ( - "context" - "crypto/md5" - "encoding/binary" - "fmt" - "os" - "strings" - - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/pkg/appctx" - "golang.org/x/sys/windows" -) - -// calcEtag will create an etag based on the md5 of -// - mtime, -// - inode (if available), -// - device (if available) and -// - size. -// errors are logged, but an etag will still be returned -func calcEtag(ctx context.Context, fi os.FileInfo) string { - log := appctx.GetLogger(ctx) - h := md5.New() - err := binary.Write(h, binary.BigEndian, fi.ModTime().UnixNano()) - if err != nil { - log.Error().Err(err).Msg("error writing mtime") - } - // device and inode have no meaning on windows - err = binary.Write(h, binary.BigEndian, fi.Size()) - if err != nil { - log.Error().Err(err).Msg("error writing size") - } - etag := fmt.Sprintf(`"%x"`, h.Sum(nil)) - return fmt.Sprintf("\"%s\"", strings.Trim(etag, "\"")) -} - -func (fs *ocfs) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, uint64, error) { - // TODO quota of which storage space? - // we could use the logged in user, but when a user has access to multiple storages this falls short - // for now return quota of root - var free, total, avail uint64 - - pathPtr, err := windows.UTF16PtrFromString(fs.toInternalPath(ctx, "/")) - if err != nil { - return 0, 0, err - } - err = windows.GetDiskFreeSpaceEx(pathPtr, &avail, &total, &free) - if err != nil { - return 0, 0, err - } - - used := total - free - return total, used, nil -} diff --git a/pkg/storage/fs/owncloud/upload.go b/pkg/storage/fs/owncloud/upload.go deleted file mode 100644 index 69a48d25d86..00000000000 --- a/pkg/storage/fs/owncloud/upload.go +++ /dev/null @@ -1,556 +0,0 @@ -// Copyright 2018-2021 CERN -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package owncloud - -import ( - "context" - "crypto/md5" - "crypto/sha1" - "encoding/hex" - "encoding/json" - "fmt" - "hash/adler32" - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/pkg/appctx" - ctxpkg "github.com/cs3org/reva/pkg/ctx" - "github.com/cs3org/reva/pkg/errtypes" - "github.com/cs3org/reva/pkg/logger" - "github.com/cs3org/reva/pkg/storage/utils/chunking" - "github.com/cs3org/reva/pkg/storage/utils/templates" - "github.com/cs3org/reva/pkg/utils" - "github.com/google/uuid" - "github.com/pkg/errors" - "github.com/pkg/xattr" - "github.com/rs/zerolog" - tusd "github.com/tus/tusd/pkg/handler" -) - -var defaultFilePerm = os.FileMode(0664) - -func (fs *ocfs) Upload(ctx context.Context, ref *provider.Reference, r io.ReadCloser) error { - upload, err := fs.GetUpload(ctx, ref.GetPath()) - if err != nil { - return errors.Wrap(err, "ocfs: error retrieving upload") - } - - uploadInfo := upload.(*fileUpload) - - p := uploadInfo.info.Storage["InternalDestination"] - ok, err := chunking.IsChunked(p) - if err != nil { - return errors.Wrap(err, "ocfs: error checking path") - } - if ok { - var assembledFile string - p, assembledFile, err = fs.chunkHandler.WriteChunk(p, r) - if err != nil { - return err - } - if p == "" { - if err = uploadInfo.Terminate(ctx); err != nil { - return errors.Wrap(err, "ocfs: error removing auxiliary files") - } - return errtypes.PartialContent(ref.String()) - } - uploadInfo.info.Storage["InternalDestination"] = p - fd, err := os.Open(assembledFile) - if err != nil { - return errors.Wrap(err, "ocfs: error opening assembled file") - } - defer fd.Close() - defer os.RemoveAll(assembledFile) - r = fd - } - - if _, err := uploadInfo.WriteChunk(ctx, 0, r); err != nil { - return errors.Wrap(err, "ocfs: error writing to binary file") - } - - return uploadInfo.FinishUpload(ctx) -} - -// InitiateUpload returns upload ids corresponding to different protocols it supports -// TODO read optional content for small files in this request -func (fs *ocfs) InitiateUpload(ctx context.Context, ref *provider.Reference, uploadLength int64, metadata map[string]string) (map[string]string, error) { - ip, err := fs.resolve(ctx, ref) - if err != nil { - return nil, errors.Wrap(err, "ocfs: error resolving reference") - } - - // permissions are checked in NewUpload below - - p := fs.toStoragePath(ctx, ip) - - info := tusd.FileInfo{ - MetaData: tusd.MetaData{ - "filename": filepath.Base(p), - "dir": filepath.Dir(p), - }, - Size: uploadLength, - } - - if metadata != nil { - if metadata["mtime"] != "" { - info.MetaData["mtime"] = metadata["mtime"] - } - if _, ok := metadata["sizedeferred"]; ok { - info.SizeIsDeferred = true - } - } - - upload, err := fs.NewUpload(ctx, info) - if err != nil { - return nil, err - } - - info, _ = upload.GetInfo(ctx) - - return map[string]string{ - "simple": info.ID, - "tus": info.ID, - }, nil -} - -// UseIn tells the tus upload middleware which extensions it supports. -func (fs *ocfs) UseIn(composer *tusd.StoreComposer) { - composer.UseCore(fs) - composer.UseTerminater(fs) - composer.UseConcater(fs) - composer.UseLengthDeferrer(fs) -} - -// To implement the core tus.io protocol as specified in https://tus.io/protocols/resumable-upload.html#core-protocol -// - the storage needs to implement NewUpload and GetUpload -// - the upload needs to implement the tusd.Upload interface: WriteChunk, GetInfo, GetReader and FinishUpload - -func (fs *ocfs) NewUpload(ctx context.Context, info tusd.FileInfo) (upload tusd.Upload, err error) { - - log := appctx.GetLogger(ctx) - log.Debug().Interface("info", info).Msg("ocfs: NewUpload") - - if info.MetaData["filename"] == "" { - return nil, errors.New("ocfs: missing filename in metadata") - } - info.MetaData["filename"] = filepath.Clean(info.MetaData["filename"]) - - dir := info.MetaData["dir"] - if dir == "" { - return nil, errors.New("ocfs: missing dir in metadata") - } - info.MetaData["dir"] = filepath.Clean(info.MetaData["dir"]) - - ip := fs.toInternalPath(ctx, filepath.Join(info.MetaData["dir"], info.MetaData["filename"])) - - // check permissions - var perm *provider.ResourcePermissions - var perr error - // if destination exists - if _, err := os.Stat(ip); err == nil { - // check permissions of file to be overwritten - perm, perr = fs.readPermissions(ctx, ip) - } else { - // check permissions of parent folder - perm, perr = fs.readPermissions(ctx, filepath.Dir(ip)) - } - if perr == nil { - if !perm.InitiateFileUpload { - return nil, errtypes.PermissionDenied("") - } - } else { - if os.IsNotExist(err) { - return nil, errtypes.NotFound(fs.toStoragePath(ctx, filepath.Dir(ip))) - } - return nil, errors.Wrap(err, "ocfs: error reading permissions") - } - - log.Debug().Interface("info", info).Msg("ocfs: resolved filename") - - info.ID = uuid.New().String() - - binPath, err := fs.getUploadPath(ctx, info.ID) - if err != nil { - return nil, errors.Wrap(err, "ocfs: error resolving upload path") - } - usr := ctxpkg.ContextMustGetUser(ctx) - info.Storage = map[string]string{ - "Type": "OwnCloudStore", - "BinPath": binPath, - "InternalDestination": ip, - - "Idp": usr.Id.Idp, - "UserId": usr.Id.OpaqueId, - "UserType": utils.UserTypeToString(usr.Id.Type), - "UserName": usr.Username, - - "LogLevel": log.GetLevel().String(), - } - // Create binary file in the upload folder with no content - log.Debug().Interface("info", info).Msg("ocfs: built storage info") - file, err := os.OpenFile(binPath, os.O_CREATE|os.O_WRONLY, defaultFilePerm) - if err != nil { - return nil, err - } - defer file.Close() - - u := &fileUpload{ - info: info, - binPath: binPath, - infoPath: filepath.Join(fs.c.UploadInfoDir, info.ID+".info"), - fs: fs, - ctx: ctx, - } - - // writeInfo creates the file by itself if necessary - err = u.writeInfo() - if err != nil { - return nil, err - } - - return u, nil -} - -func (fs *ocfs) getUploadPath(ctx context.Context, uploadID string) (string, error) { - u, ok := ctxpkg.ContextGetUser(ctx) - if !ok { - err := errors.Wrap(errtypes.UserRequired("userrequired"), "error getting user from ctx") - return "", err - } - layout := templates.WithUser(u, fs.c.UserLayout) - return filepath.Join(fs.c.DataDirectory, layout, "uploads", uploadID), nil -} - -// GetUpload returns the Upload for the given upload id -func (fs *ocfs) GetUpload(ctx context.Context, id string) (tusd.Upload, error) { - infoPath := filepath.Join(fs.c.UploadInfoDir, filepath.Join("/", id+".info")) - - info := tusd.FileInfo{} - data, err := ioutil.ReadFile(infoPath) - if err != nil { - return nil, err - } - if err := json.Unmarshal(data, &info); err != nil { - return nil, err - } - - stat, err := os.Stat(info.Storage["BinPath"]) - if err != nil { - return nil, err - } - - info.Offset = stat.Size() - - u := &userpb.User{ - Id: &userpb.UserId{ - Idp: info.Storage["Idp"], - OpaqueId: info.Storage["UserId"], - Type: utils.UserTypeMap(info.Storage["UserType"]), - }, - Username: info.Storage["UserName"], - } - - ctx = ctxpkg.ContextSetUser(ctx, u) - // TODO configure the logger the same way ... store and add traceid in file info - - var opts []logger.Option - opts = append(opts, logger.WithLevel(info.Storage["LogLevel"])) - opts = append(opts, logger.WithWriter(os.Stderr, logger.ConsoleMode)) - l := logger.New(opts...) - - sub := l.With().Int("pid", os.Getpid()).Logger() - - ctx = appctx.WithLogger(ctx, &sub) - - return &fileUpload{ - info: info, - binPath: info.Storage["BinPath"], - infoPath: infoPath, - fs: fs, - ctx: ctx, - }, nil -} - -type fileUpload struct { - // info stores the current information about the upload - info tusd.FileInfo - // infoPath is the path to the .info file - infoPath string - // binPath is the path to the binary file (which has no extension) - binPath string - // only fs knows how to handle metadata and versions - fs *ocfs - // a context with a user - // TODO add logger as well? - ctx context.Context -} - -// GetInfo returns the FileInfo -func (upload *fileUpload) GetInfo(ctx context.Context) (tusd.FileInfo, error) { - return upload.info, nil -} - -// WriteChunk writes the stream from the reader to the given offset of the upload -func (upload *fileUpload) WriteChunk(ctx context.Context, offset int64, src io.Reader) (int64, error) { - file, err := os.OpenFile(upload.binPath, os.O_WRONLY|os.O_APPEND, defaultFilePerm) - if err != nil { - return 0, err - } - defer file.Close() - - n, err := io.Copy(file, src) - - // If the HTTP PATCH request gets interrupted in the middle (e.g. because - // the user wants to pause the upload), Go's net/http returns an io.ErrUnexpectedEOF. - // However, for OwnCloudStore it's not important whether the stream has ended - // on purpose or accidentally. - if err != nil { - if err != io.ErrUnexpectedEOF { - return n, err - } - } - - upload.info.Offset += n - err = upload.writeInfo() // TODO info is written here ... we need to truncate in DiscardChunk - - return n, err -} - -// GetReader returns an io.Reader for the upload -func (upload *fileUpload) GetReader(ctx context.Context) (io.Reader, error) { - return os.Open(upload.binPath) -} - -// writeInfo updates the entire information. Everything will be overwritten. -func (upload *fileUpload) writeInfo() error { - data, err := json.Marshal(upload.info) - if err != nil { - return err - } - return ioutil.WriteFile(upload.infoPath, data, defaultFilePerm) -} - -// FinishUpload finishes an upload and moves the file to the internal destination -func (upload *fileUpload) FinishUpload(ctx context.Context) error { - log := appctx.GetLogger(upload.ctx) - - sha1Sum := make([]byte, 0, 32) - md5Sum := make([]byte, 0, 32) - adler32Sum := make([]byte, 0, 32) - { - sha1h := sha1.New() - md5h := md5.New() - adler32h := adler32.New() - f, err := os.Open(upload.binPath) - if err != nil { - log.Err(err).Msg("Decomposedfs: could not open file for checksumming") - // we can continue if no oc checksum header is set - } - defer f.Close() - - r1 := io.TeeReader(f, sha1h) - r2 := io.TeeReader(r1, md5h) - - if _, err := io.Copy(adler32h, r2); err != nil { - log.Err(err).Msg("Decomposedfs: could not copy bytes for checksumming") - } - - sha1Sum = sha1h.Sum(sha1Sum) - md5Sum = md5h.Sum(md5Sum) - adler32Sum = adler32h.Sum(adler32Sum) - } - - if upload.info.MetaData["checksum"] != "" { - parts := strings.SplitN(upload.info.MetaData["checksum"], " ", 2) - if len(parts) != 2 { - return errtypes.BadRequest("invalid checksum format. must be '[algorithm] [checksum]'") - } - var err error - switch parts[0] { - case "sha1": - err = upload.checkHash(parts[1], sha1Sum) - case "md5": - err = upload.checkHash(parts[1], md5Sum) - case "adler32": - err = upload.checkHash(parts[1], adler32Sum) - default: - err = errtypes.BadRequest("unsupported checksum algorithm: " + parts[0]) - } - if err != nil { - return err - } - } - - ip := upload.info.Storage["InternalDestination"] - - // if destination exists - // TODO check etag with If-Match header - if _, err := os.Stat(ip); err == nil { - // copy attributes of existing file to tmp file - if err := upload.fs.copyMD(ip, upload.binPath); err != nil { - return errors.Wrap(err, "ocfs: error copying metadata from "+ip+" to "+upload.binPath) - } - // create revision - if err := upload.fs.archiveRevision(upload.ctx, upload.fs.getVersionsPath(upload.ctx, ip), ip); err != nil { - return err - } - } - - err := os.Rename(upload.binPath, ip) - if err != nil { - log.Err(err).Interface("info", upload.info). - Str("binPath", upload.binPath). - Str("ipath", ip). - Msg("ocfs: could not rename") - return err - } - - // only delete the upload if it was successfully written to the storage - if err := os.Remove(upload.infoPath); err != nil { - if !os.IsNotExist(err) { - log.Err(err).Interface("info", upload.info).Msg("ocfs: could not delete upload info") - return err - } - } - - if upload.info.MetaData["mtime"] != "" { - err := upload.fs.setMtime(ctx, ip, upload.info.MetaData["mtime"]) - if err != nil { - log.Err(err).Interface("info", upload.info).Msg("ocfs: could not set mtime metadata") - return err - } - } - - // now try write all checksums - tryWritingChecksum(log, ip, "sha1", sha1Sum) - tryWritingChecksum(log, ip, "md5", md5Sum) - tryWritingChecksum(log, ip, "adler32", adler32Sum) - - return upload.fs.propagate(upload.ctx, ip) -} - -// To implement the termination extension as specified in https://tus.io/protocols/resumable-upload.html#termination -// - the storage needs to implement AsTerminatableUpload -// - the upload needs to implement Terminate - -// AsTerminatableUpload returns a TerminatableUpload -func (fs *ocfs) AsTerminatableUpload(upload tusd.Upload) tusd.TerminatableUpload { - return upload.(*fileUpload) -} - -// Terminate terminates the upload -func (upload *fileUpload) Terminate(ctx context.Context) error { - if err := os.Remove(upload.infoPath); err != nil { - if !os.IsNotExist(err) { - return err - } - } - if err := os.Remove(upload.binPath); err != nil { - if !os.IsNotExist(err) { - return err - } - } - return nil -} - -// To implement the creation-defer-length extension as specified in https://tus.io/protocols/resumable-upload.html#creation -// - the storage needs to implement AsLengthDeclarableUpload -// - the upload needs to implement DeclareLength - -// AsLengthDeclarableUpload returns a LengthDeclarableUpload -func (fs *ocfs) AsLengthDeclarableUpload(upload tusd.Upload) tusd.LengthDeclarableUpload { - return upload.(*fileUpload) -} - -// DeclareLength updates the upload length information -func (upload *fileUpload) DeclareLength(ctx context.Context, length int64) error { - upload.info.Size = length - upload.info.SizeIsDeferred = false - return upload.writeInfo() -} - -// To implement the concatenation extension as specified in https://tus.io/protocols/resumable-upload.html#concatenation -// - the storage needs to implement AsConcatableUpload -// - the upload needs to implement ConcatUploads - -// AsConcatableUpload returns a ConcatableUpload -func (fs *ocfs) AsConcatableUpload(upload tusd.Upload) tusd.ConcatableUpload { - return upload.(*fileUpload) -} - -// ConcatUploads concatenates multiple uploads -func (upload *fileUpload) ConcatUploads(ctx context.Context, uploads []tusd.Upload) (err error) { - file, err := os.OpenFile(upload.binPath, os.O_WRONLY|os.O_APPEND, defaultFilePerm) - if err != nil { - return err - } - defer file.Close() - - for _, partialUpload := range uploads { - fileUpload := partialUpload.(*fileUpload) - - src, err := os.Open(fileUpload.binPath) - if err != nil { - return err - } - - if _, err := io.Copy(file, src); err != nil { - return err - } - } - - return -} - -func (upload *fileUpload) checkHash(expected string, h []byte) error { - if expected != hex.EncodeToString(h) { - upload.discardChunk() - return errtypes.ChecksumMismatch(fmt.Sprintf("invalid checksum: expected %s got %x", upload.info.MetaData["checksum"], h)) - } - return nil -} - -func (upload *fileUpload) discardChunk() { - if err := os.Remove(upload.binPath); err != nil { - if !os.IsNotExist(err) { - appctx.GetLogger(upload.ctx).Err(err).Interface("info", upload.info).Str("binPath", upload.binPath).Interface("info", upload.info).Msg("Decomposedfs: could not discard chunk") - return - } - } - if err := os.Remove(upload.infoPath); err != nil { - if !os.IsNotExist(err) { - appctx.GetLogger(upload.ctx).Err(err).Interface("info", upload.info).Str("infoPath", upload.infoPath).Interface("info", upload.info).Msg("Decomposedfs: could not discard chunk info") - return - } - } -} - -func tryWritingChecksum(log *zerolog.Logger, path, algo string, h []byte) { - if err := xattr.Set(path, checksumPrefix+algo, h); err != nil { - log.Err(err). - Str("csType", algo). - Bytes("hash", h). - Msg("ocfs: could not write checksum") - } -} diff --git a/tests/integration/grpc/fixtures/storageprovider-owncloud.toml b/tests/integration/grpc/fixtures/storageprovider-owncloud.toml deleted file mode 100644 index 1cd808cea62..00000000000 --- a/tests/integration/grpc/fixtures/storageprovider-owncloud.toml +++ /dev/null @@ -1,12 +0,0 @@ -[grpc] -address = "{{grpc_address}}" - -[grpc.services.storageprovider] -driver = "owncloud" - -[grpc.services.storageprovider.drivers.owncloud] -enable_home = {{enable_home}} -datadirectory = "{{root}}/storage" -userprovidersvc = "{{users_address}}" -mount_id = "{{id}}" -redis = "{{redis_address}}" \ No newline at end of file diff --git a/tests/integration/grpc/storageprovider_test.go b/tests/integration/grpc/storageprovider_test.go index 36dac5e65cc..2b005eaec57 100644 --- a/tests/integration/grpc/storageprovider_test.go +++ b/tests/integration/grpc/storageprovider_test.go @@ -20,7 +20,6 @@ package grpc_test import ( "context" - "os" "google.golang.org/grpc/metadata" @@ -32,7 +31,6 @@ import ( "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/pkg/storage" "github.com/cs3org/reva/pkg/storage/fs/ocis" - "github.com/cs3org/reva/pkg/storage/fs/owncloud" jwt "github.com/cs3org/reva/pkg/token/manager/jwt" "github.com/cs3org/reva/tests/helpers" @@ -64,11 +62,6 @@ func createFS(provider string, revads map[string]*Revad) (storage.FS, error) { conf["root"] = revads["storage"].StorageRoot conf["enable_home"] = true f = ocis.New - case "owncloud": - conf["datadirectory"] = revads["storage"].StorageRoot - conf["userprovidersvc"] = revads["users"].GrpcAddress - conf["enable_home"] = true - f = owncloud.New } return f(conf) } @@ -183,7 +176,7 @@ var _ = Describe("storage providers", func() { switch provider { case "ocis": Expect(len(listRes.Infos)).To(Equal(2)) // subdir + .space - case "owncloud", "nextcloud": + case "nextcloud": Expect(len(listRes.Infos)).To(Equal(1)) // subdir default: Fail("unknown provider") @@ -288,10 +281,8 @@ var _ = Describe("storage providers", func() { Expect(err).ToNot(HaveOccurred()) // TODO: FIXME both cases should work for all providers - if provider != "owncloud" { - Expect(res.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) - } - if provider != "nextcloud" && provider != "owncloud" { + Expect(res.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + if provider != "nextcloud" { Expect(res.Path).To(Equal(subdirPath)) } }) @@ -558,13 +549,6 @@ var _ = Describe("storage providers", func() { variables = map[string]string{ "enable_home": "true", } - if provider == "owncloud" { - redisAddress := os.Getenv("REDIS_ADDRESS") - if redisAddress == "" { - Fail("REDIS_ADDRESS not set") - } - variables["redis_address"] = redisAddress - } }) assertCreateHome(provider) @@ -637,8 +621,4 @@ var _ = Describe("storage providers", func() { "storage": "storageprovider-ocis.toml", }) - suite("owncloud", map[string]string{ - "users": "userprovider-json.toml", - "storage": "storageprovider-owncloud.toml", - }) }) From 1143238269c7c790b8442fde38eda16ae12a4f65 Mon Sep 17 00:00:00 2001 From: David Christofas Date: Thu, 3 Feb 2022 16:21:45 +0100 Subject: [PATCH 29/49] fix spaces stat requests (#2501) --- changelog/unreleased/spaces-stat.md | 6 ++ .../grpc/services/gateway/storageprovider.go | 62 ++++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 changelog/unreleased/spaces-stat.md diff --git a/changelog/unreleased/spaces-stat.md b/changelog/unreleased/spaces-stat.md new file mode 100644 index 00000000000..102e86967c5 --- /dev/null +++ b/changelog/unreleased/spaces-stat.md @@ -0,0 +1,6 @@ +Bugfix: Fix spaces stat + +When stating a space e.g. the Share Jail and that space contains another space, in this case a share +then the stat would sometimes get the sub space instead of the Share Jail itself. + +https://github.com/cs3org/reva/pull/2501 diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index 16d800fd8ef..58c191be8bf 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -802,7 +802,7 @@ func (s *svc) Unlock(ctx context.Context, req *provider.UnlockRequest) (*provide // Stat returns the Resoure info for a given resource by forwarding the request to the responsible provider. // TODO cache info func (s *svc) Stat(ctx context.Context, req *provider.StatRequest) (*provider.StatResponse, error) { - c, _, ref, err := s.findAndUnwrap(ctx, req.Ref) + c, _, ref, err := s.findAndUnwrapUnique(ctx, req.Ref) if err != nil { return &provider.StatResponse{ Status: status.NewNotFound(ctx, fmt.Sprintf("gateway could not find space for ref=%+v", req.Ref)), @@ -991,6 +991,16 @@ func (s *svc) find(ctx context.Context, ref *provider.Reference) (provider.Provi return client, p[0], err } +func (s *svc) findUnique(ctx context.Context, ref *provider.Reference) (provider.ProviderAPIClient, *registry.ProviderInfo, error) { + p, err := s.findSingleSpace(ctx, ref) + if err != nil { + return nil, nil, err + } + + client, err := s.getStorageProviderClient(ctx, p[0]) + return client, p[0], err +} + // FIXME findAndUnwrap currently just returns the first provider ... which may not be what is needed. // for the ListRecycle call we need an exact match, for Stat and List we need to query all related providers func (s *svc) findAndUnwrap(ctx context.Context, ref *provider.Reference) (provider.ProviderAPIClient, *registry.ProviderInfo, *provider.Reference, error) { @@ -1014,6 +1024,27 @@ func (s *svc) findAndUnwrap(ctx context.Context, ref *provider.Reference) (provi return c, p, relativeReference, nil } +func (s *svc) findAndUnwrapUnique(ctx context.Context, ref *provider.Reference) (provider.ProviderAPIClient, *registry.ProviderInfo, *provider.Reference, error) { + c, p, err := s.findUnique(ctx, ref) + if err != nil { + return nil, nil, nil, err + } + + var ( + root *provider.ResourceId + mountPath string + ) + for _, space := range decodeSpaces(p) { + mountPath = decodePath(space) + root = space.Root + break // TODO can there be more than one space for a path? + } + + relativeReference := unwrap(ref, mountPath, root) + + return c, p, relativeReference, nil +} + func (s *svc) getStorageProviderClient(_ context.Context, p *registry.ProviderInfo) (provider.ProviderAPIClient, error) { c, err := pool.GetStorageProviderServiceClient(p.Address) if err != nil { @@ -1060,6 +1091,35 @@ func (s *svc) findSpaces(ctx context.Context, ref *provider.Reference) ([]*regis return s.findProvider(ctx, listReq) } +func (s *svc) findSingleSpace(ctx context.Context, ref *provider.Reference) ([]*registry.ProviderInfo, error) { + switch { + case ref == nil: + return nil, errtypes.BadRequest("missing reference") + case ref.ResourceId != nil: + // no action needed in that case + case ref.Path != "": // TODO implement a mount path cache in the registry? + // nothing to do here either + default: + return nil, errtypes.BadRequest("invalid reference, at least path or id must be set") + } + + filters := map[string]string{ + "path": ref.Path, + "unique": "true", + } + if ref.ResourceId != nil { + filters["storage_id"] = ref.ResourceId.StorageId + filters["opaque_id"] = ref.ResourceId.OpaqueId + } + + listReq := ®istry.ListStorageProvidersRequest{ + Opaque: &typesv1beta1.Opaque{}, + } + sdk.EncodeOpaqueMap(listReq.Opaque, filters) + + return s.findProvider(ctx, listReq) +} + func (s *svc) findProvider(ctx context.Context, listReq *registry.ListStorageProvidersRequest) ([]*registry.ProviderInfo, error) { // lookup c, err := pool.GetStorageRegistryClient(s.c.StorageRegistryEndpoint) From 48c06f97f7366c30a9195c4c4ed95d9e8b418885 Mon Sep 17 00:00:00 2001 From: Michael Barz Date: Thu, 3 Feb 2022 16:52:34 +0100 Subject: [PATCH 30/49] invalidate cache when modifying or deleting a space (#2500) --- .../unreleased/invalidate-listproviders-cache.md | 5 +++++ .../grpc/services/gateway/storageprovider.go | 9 +++++++-- .../services/gateway/storageprovidercache.go | 16 ++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 changelog/unreleased/invalidate-listproviders-cache.md diff --git a/changelog/unreleased/invalidate-listproviders-cache.md b/changelog/unreleased/invalidate-listproviders-cache.md new file mode 100644 index 00000000000..7566c492ba8 --- /dev/null +++ b/changelog/unreleased/invalidate-listproviders-cache.md @@ -0,0 +1,5 @@ +Enhancement: Invalidate listproviders cache + +We now invalidate the related listproviders cache entries when updating or deleting a storage space. + +https://github.com/cs3org/reva/pull/2500 \ No newline at end of file diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index 58c191be8bf..6aebda5f5a2 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -285,7 +285,10 @@ func (s *svc) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorag Status: status.NewStatusFromErrType(ctx, "gateway could not call UpdateStorageSpace", err), }, nil } - s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), res.StorageSpace.Root) + + id := res.StorageSpace.Root + s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), id) + s.cache.RemoveListStorageProviders(id) return res, nil } @@ -322,7 +325,9 @@ func (s *svc) DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorag }, nil } - s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), &provider.ResourceId{OpaqueId: req.Id.OpaqueId}) + id := &provider.ResourceId{OpaqueId: req.Id.OpaqueId} + s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), id) + s.cache.RemoveListStorageProviders(id) if dsRes.Status.Code != rpc.Code_CODE_OK { return dsRes, nil diff --git a/internal/grpc/services/gateway/storageprovidercache.go b/internal/grpc/services/gateway/storageprovidercache.go index 3100f126317..6a5ee43028c 100644 --- a/internal/grpc/services/gateway/storageprovidercache.go +++ b/internal/grpc/services/gateway/storageprovidercache.go @@ -128,6 +128,22 @@ func (c Caches) RemoveStat(user *userpb.User, res *provider.ResourceId) { } } +// RemoveListStorageProviders removes a reference from the listproviders cache +func (c Caches) RemoveListStorageProviders(res *provider.ResourceId) { + if res == nil { + return + } + sid := res.StorageId + + cache := c[listproviders] + for _, key := range cache.GetKeys() { + if strings.Contains(key, sid) { + _ = cache.Remove(key) + continue + } + } +} + func initCache(ttlSeconds int) *ttlcache.Cache { cache := ttlcache.NewCache() _ = cache.SetTTL(time.Duration(ttlSeconds) * time.Second) From da5be8579223d4a429ba621280a307e7518ef2f3 Mon Sep 17 00:00:00 2001 From: David Christofas Date: Thu, 3 Feb 2022 17:54:58 +0100 Subject: [PATCH 31/49] add grants to list-spaces (#2498) When listing storage spaces we also want to have the spaces grants to be able to show who has access to the space. Co-authored-by: kobergj --- changelog/unreleased/spaces-grants.md | 5 ++ pkg/storage/utils/decomposedfs/node/node.go | 26 ++++++++- pkg/storage/utils/decomposedfs/spaces.go | 59 ++++++++++++++++----- 3 files changed, 76 insertions(+), 14 deletions(-) create mode 100644 changelog/unreleased/spaces-grants.md diff --git a/changelog/unreleased/spaces-grants.md b/changelog/unreleased/spaces-grants.md new file mode 100644 index 00000000000..3013ebc8aee --- /dev/null +++ b/changelog/unreleased/spaces-grants.md @@ -0,0 +1,5 @@ +Enhancement: Include grants in list storage spaces response + +Added the grants to the response of list storage spaces. This allows service clients to show who has access to a space. + +https://github.com/cs3org/reva/pull/2498 diff --git a/pkg/storage/utils/decomposedfs/node/node.go b/pkg/storage/utils/decomposedfs/node/node.go index 273fac5ed46..9be1a2d09f1 100644 --- a/pkg/storage/utils/decomposedfs/node/node.go +++ b/pkg/storage/utils/decomposedfs/node/node.go @@ -879,7 +879,7 @@ func (n *Node) ReadUserPermissions(ctx context.Context, u *userpb.User) (ap prov func (n *Node) ListGrantees(ctx context.Context) (grantees []string, err error) { var attrs []string if attrs, err = xattr.List(n.InternalPath()); err != nil { - appctx.GetLogger(ctx).Error().Err(err).Interface("node", n).Msg("error listing attributes") + appctx.GetLogger(ctx).Error().Err(err).Str("node", n.ID).Msg("error listing attributes") return nil, err } for i := range attrs { @@ -903,6 +903,30 @@ func (n *Node) ReadGrant(ctx context.Context, grantee string) (g *provider.Grant return e.Grant(), nil } +// ListGrants lists all grants of the current node. +func (n *Node) ListGrants(ctx context.Context) ([]*provider.Grant, error) { + grantees, err := n.ListGrantees(ctx) + if err != nil { + return nil, err + } + + grants := make([]*provider.Grant, 0, len(grantees)) + for _, g := range grantees { + grant, err := n.ReadGrant(ctx, g) + if err != nil { + appctx.GetLogger(ctx). + Error(). + Err(err). + Str("node", n.ID). + Str("grantee", g). + Msg("error reading grant") + continue + } + grants = append(grants, grant) + } + return grants, nil +} + // ReadBlobSizeAttr reads the blobsize from the xattrs func ReadBlobSizeAttr(path string) (int64, error) { attrBytes, err := xattr.Get(path, xattrs.BlobsizeAttr) diff --git a/pkg/storage/utils/decomposedfs/spaces.go b/pkg/storage/utils/decomposedfs/spaces.go index 877718b239e..a380eedc6eb 100644 --- a/pkg/storage/utils/decomposedfs/spaces.go +++ b/pkg/storage/utils/decomposedfs/spaces.go @@ -20,6 +20,7 @@ package decomposedfs import ( "context" + "encoding/json" "fmt" "math" "os" @@ -478,6 +479,19 @@ func (fs *Decomposedfs) createStorageSpace(ctx context.Context, spaceType, space } func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, spaceType, nodePath string, canListAllSpaces bool) (*provider.StorageSpace, error) { + user := ctxpkg.ContextMustGetUser(ctx) + if !canListAllSpaces { + ok, err := node.NewPermissions(fs.lu).HasPermission(ctx, n, func(p *provider.ResourcePermissions) bool { + return p.Stat + }) + if err != nil || !ok { + return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to Stat the space %s", user.Username, n.SpaceRoot.ID)) + } + if n.SpaceRoot != nil && strings.Contains(n.SpaceRoot.ID, node.TrashIDDelimiter) { + return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to list deleted spaces %s", user.Username, n.SpaceRoot.ID)) + } + } + owner, err := n.Owner() if err != nil { return nil, err @@ -506,7 +520,39 @@ func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, spaceType = filepath.Base(filepath.Dir(matches[0])) + grants, err := n.ListGrants(ctx) + if err != nil { + return nil, err + } + + m := make(map[string]*provider.ResourcePermissions, len(grants)) + for _, g := range grants { + var id string + switch g.Grantee.Type { + case provider.GranteeType_GRANTEE_TYPE_GROUP: + id = g.Grantee.GetGroupId().OpaqueId + case provider.GranteeType_GRANTEE_TYPE_USER: + id = g.Grantee.GetUserId().OpaqueId + default: + continue + } + + m[id] = g.Permissions + } + marshalled, err := json.Marshal(m) + if err != nil { + return nil, err + } + space := &provider.StorageSpace{ + Opaque: &types.Opaque{ + Map: map[string]*types.OpaqueEntry{ + "grants": { + Decoder: "json", + Value: marshalled, + }, + }, + }, Id: &provider.StorageSpaceId{OpaqueId: n.SpaceRoot.ID}, Root: &provider.ResourceId{ StorageId: n.SpaceRoot.ID, @@ -517,19 +563,6 @@ func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, // Mtime is set either as node.tmtime or as fi.mtime below } - user := ctxpkg.ContextMustGetUser(ctx) - if !canListAllSpaces { - ok, err := node.NewPermissions(fs.lu).HasPermission(ctx, n, func(p *provider.ResourcePermissions) bool { - return p.Stat - }) - if err != nil || !ok { - return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to Stat the space %+v", user.Username, space)) - } - if strings.Contains(space.Root.OpaqueId, node.TrashIDDelimiter) { - return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to list deleted spaces %+v", user.Username, space)) - } - } - space.Owner = &userv1beta1.User{ // FIXME only return a UserID, not a full blown user object Id: owner, } From c3bb73f545af5c7cf40a133e5adeb369e6579b12 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Fri, 4 Feb 2022 14:13:58 +0545 Subject: [PATCH 32/49] Bump CORE_COMMITID for API tests (#2496) --- .drone.env | 2 +- .../expected-failures-on-OCIS-storage.md | 122 ++++++++--------- .../expected-failures-on-S3NG-storage.md | 129 +++++++++--------- 3 files changed, 126 insertions(+), 127 deletions(-) diff --git a/.drone.env b/.drone.env index 4f28f08f0f6..51b94a70897 100644 --- a/.drone.env +++ b/.drone.env @@ -1,3 +1,3 @@ # The test runner source for API tests -CORE_COMMITID=0dadfbe475438dd97c192cb93643ef8d95b71faa +CORE_COMMITID=2880ab5d326b86336bff29e6709adf774a317d88 CORE_BRANCH=master diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index c53c1fe6080..49630049afa 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -14,17 +14,17 @@ These tests succeed when running against ocis because there we handle the releva #### [Getting information about a folder overwritten by a file gives 500 error instead of 404](https://github.com/owncloud/ocis/issues/1239) These tests are about overwriting files or folders in the `Shares` folder of a user. -- [apiWebdavProperties1/copyFile.feature:226](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L226) -- [apiWebdavProperties1/copyFile.feature:227](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L227) -- [apiWebdavProperties1/copyFile.feature:244](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L244) -- [apiWebdavProperties1/copyFile.feature:245](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L245) +- [apiWebdavProperties1/copyFile.feature:287](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L287) +- [apiWebdavProperties1/copyFile.feature:288](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L288) +- [apiWebdavProperties1/copyFile.feature:310](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L310) +- [apiWebdavProperties1/copyFile.feature:311](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L311) #### [Custom dav properties with namespaces are rendered incorrectly](https://github.com/owncloud/ocis/issues/2140) _ocdav: double check the webdav property parsing when custom namespaces are used_ -- [apiWebdavProperties1/setFileProperties.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L32) -- [apiWebdavProperties1/setFileProperties.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L33) -- [apiWebdavProperties1/setFileProperties.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L63) -- [apiWebdavProperties1/setFileProperties.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L64) +- [apiWebdavProperties1/setFileProperties.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L37) +- [apiWebdavProperties1/setFileProperties.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L38) +- [apiWebdavProperties1/setFileProperties.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L78) +- [apiWebdavProperties1/setFileProperties.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L79) #### [Cannot set custom webDav properties](https://github.com/owncloud/product/issues/264) - [apiWebdavProperties2/getFileProperties.feature:348](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L348) @@ -105,12 +105,12 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks/publicLink.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L35) - [apiWebdavLocks/publicLink.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L49) - [apiWebdavLocks/publicLink.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L50) -- [apiWebdavLocks/publicLink.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L62) -- [apiWebdavLocks/publicLink.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L63) -- [apiWebdavLocks/publicLink.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L77) -- [apiWebdavLocks/publicLink.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L78) -- [apiWebdavLocks/publicLink.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L97) -- [apiWebdavLocks/publicLink.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L98) +- [apiWebdavLocks/publicLink.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L67) +- [apiWebdavLocks/publicLink.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L68) +- [apiWebdavLocks/publicLink.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L87) +- [apiWebdavLocks/publicLink.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L88) +- [apiWebdavLocks/publicLink.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L107) +- [apiWebdavLocks/publicLink.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L108) - [apiWebdavLocks/publicLinkLockdiscovery.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L22) - [apiWebdavLocks/publicLinkLockdiscovery.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L23) - [apiWebdavLocks/publicLinkLockdiscovery.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L37) @@ -549,20 +549,20 @@ _requires a [CS3 user provisioning api that can update the quota for a user](htt - [apiMain/quota.feature:289](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/quota.feature#L289) Scenario Outline: Retrieving folder quota of shared folder with quota when no quota is set for recipient -- [apiWebdavProperties1/getQuota.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L48) -- [apiWebdavProperties1/getQuota.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L49) +- [apiWebdavProperties1/getQuota.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L58) +- [apiWebdavProperties1/getQuota.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L59) Scenario Outline: Retrieving folder quota when quota is set and a file was uploaded -- [apiWebdavProperties1/getQuota.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L61) -- [apiWebdavProperties1/getQuota.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L62) -Scenario Outline: Retrieving folder quota when quota is set and a file was received - [apiWebdavProperties1/getQuota.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L77) - [apiWebdavProperties1/getQuota.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L78) +Scenario Outline: Retrieving folder quota when quota is set and a file was received +- [apiWebdavProperties1/getQuota.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L98) +- [apiWebdavProperties1/getQuota.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L99) Scenario Outline: Retrieving folder quota when no quota is set - [apiWebdavProperties1/getQuota.feature:17](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L17) - [apiWebdavProperties1/getQuota.feature:18](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L18) Scenario Outline: Retrieving folder quota when quota is set -- [apiWebdavProperties1/getQuota.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L27) -- [apiWebdavProperties1/getQuota.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L28) +- [apiWebdavProperties1/getQuota.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L32) +- [apiWebdavProperties1/getQuota.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L33) #### [Private link support](https://github.com/owncloud/product/issues/201) #### [oc:privatelink property not returned in webdav responses](https://github.com/owncloud/product/issues/262) @@ -585,7 +585,7 @@ _requires a [CS3 user provisioning api that can update the quota for a user](htt #### [not possible to move file into a received folder](https://github.com/owncloud/ocis/issues/764) - [apiShareOperationsToShares1/changingFilesShare.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L24) - [apiShareOperationsToShares1/changingFilesShare.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L25) -- [apiShareOperationsToShares1/changingFilesShare.feature:82](https://github.com/owncloud/core/blob/master/test/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L82) +- [apiShareOperationsToShares1/changingFilesShare.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L82) - [apiShareOperationsToShares1/changingFilesShare.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L98) Scenario Outline: Moving a file into a shared folder as the sharee and as the sharer @@ -1101,10 +1101,10 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers - [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L72) #### [Blacklist files extensions](https://github.com/owncloud/ocis/issues/2177) -- [apiWebdavProperties1/copyFile.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L107) -- [apiWebdavProperties1/copyFile.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L108) -- [apiWebdavProperties1/createFolder.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L71) -- [apiWebdavProperties1/createFolder.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L72) +- [apiWebdavProperties1/copyFile.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L133) +- [apiWebdavProperties1/copyFile.feature:134](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L134) +- [apiWebdavProperties1/createFolder.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L92) +- [apiWebdavProperties1/createFolder.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L93) - [apiWebdavUpload1/uploadFile.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L132) - [apiWebdavUpload1/uploadFile.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L133) - [apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature#L19) @@ -1159,44 +1159,44 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers - [apiShareManagementToShares/mergeShare.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/mergeShare.feature#L89) #### Additional shares to the same resource (e.g. user and group share) are now auto-accepted with the existing mountpoint -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:553](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:553) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:554](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:554) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:575](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:575) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:576](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:576) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:553](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L553) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:554](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L554) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:575](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L575) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:576](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L576) ### To triage _The below features have been added after I last categorized them. AFAICT they are bugs. @jfd_ #### [PATCH request for TUS upload with wrong checksum gives incorrect response](https://github.com/owncloud/ocis/issues/1755) -- [apiWebdavUploadTUS/checksums.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L65) -- [apiWebdavUploadTUS/checksums.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L66) -- [apiWebdavUploadTUS/checksums.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L67) -- [apiWebdavUploadTUS/checksums.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L68) -- [apiWebdavUploadTUS/checksums.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L131) -- [apiWebdavUploadTUS/checksums.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L132) -- [apiWebdavUploadTUS/checksums.feature:172](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L172) -- [apiWebdavUploadTUS/checksums.feature:173](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L173) -- [apiWebdavUploadTUS/checksums.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L174) -- [apiWebdavUploadTUS/checksums.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L175) -- [apiWebdavUploadTUS/checksums.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L215) -- [apiWebdavUploadTUS/checksums.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L216) -- [apiWebdavUploadTUS/checksums.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L217) -- [apiWebdavUploadTUS/checksums.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L218) +- [apiWebdavUploadTUS/checksums.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L81) +- [apiWebdavUploadTUS/checksums.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L82) +- [apiWebdavUploadTUS/checksums.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L83) +- [apiWebdavUploadTUS/checksums.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L84) +- [apiWebdavUploadTUS/checksums.feature:168](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L168) +- [apiWebdavUploadTUS/checksums.feature:169](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L169) +- [apiWebdavUploadTUS/checksums.feature:219](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L219) +- [apiWebdavUploadTUS/checksums.feature:220](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L220) +- [apiWebdavUploadTUS/checksums.feature:221](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L221) +- [apiWebdavUploadTUS/checksums.feature:222](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L222) +- [apiWebdavUploadTUS/checksums.feature:274](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L274) +- [apiWebdavUploadTUS/checksums.feature:275](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L275) +- [apiWebdavUploadTUS/checksums.feature:276](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L276) +- [apiWebdavUploadTUS/checksums.feature:277](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L277) - [apiWebdavUploadTUS/optionsRequest.feature:7](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L7) -- [apiWebdavUploadTUS/optionsRequest.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L20) -- [apiWebdavUploadTUS/optionsRequest.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L33) -- [apiWebdavUploadTUS/optionsRequest.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L46) -- [apiWebdavUploadTUS/uploadToShare.feature:173](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L173) -- [apiWebdavUploadTUS/uploadToShare.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L174) -- [apiWebdavUploadTUS/uploadToShare.feature:192](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L192) -- [apiWebdavUploadTUS/uploadToShare.feature:193](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L193) -- [apiWebdavUploadTUS/uploadToShare.feature:211](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L211) -- [apiWebdavUploadTUS/uploadToShare.feature:212](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L212) -- [apiWebdavUploadTUS/uploadToShare.feature:248](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L248) -- [apiWebdavUploadTUS/uploadToShare.feature:249](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L249) -- [apiWebdavUploadTUS/uploadToShare.feature:289](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L289) -- [apiWebdavUploadTUS/uploadToShare.feature:290](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L290) +- [apiWebdavUploadTUS/optionsRequest.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L31) +- [apiWebdavUploadTUS/optionsRequest.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L55) +- [apiWebdavUploadTUS/optionsRequest.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L79) +- [apiWebdavUploadTUS/uploadToShare.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L216) +- [apiWebdavUploadTUS/uploadToShare.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L217) +- [apiWebdavUploadTUS/uploadToShare.feature:240](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L240) +- [apiWebdavUploadTUS/uploadToShare.feature:241](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L241) +- [apiWebdavUploadTUS/uploadToShare.feature:264](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L264) +- [apiWebdavUploadTUS/uploadToShare.feature:265](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L265) +- [apiWebdavUploadTUS/uploadToShare.feature:310](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L310) +- [apiWebdavUploadTUS/uploadToShare.feature:311](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L311) +- [apiWebdavUploadTUS/uploadToShare.feature:361](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L361) +- [apiWebdavUploadTUS/uploadToShare.feature:362](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L362) #### [Share inaccessible if folder with same name was deleted and recreated](https://github.com/owncloud/ocis/issues/1787) - [apiShareReshareToShares1/reShare.feature:269](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares1/reShare.feature#L269) @@ -1254,10 +1254,10 @@ _ocs: api compatibility, return correct status code_ - [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:455](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L455) #### [Trying to copy a file into a readonly share gives HTTP 500 error](https://github.com/owncloud/ocis/issues/2166) -- [apiWebdavProperties1/copyFile.feature:362](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L362) -- [apiWebdavProperties1/copyFile.feature:363](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L363) -- [apiWebdavProperties1/copyFile.feature:383](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L483) -- [apiWebdavProperties1/copyFile.feature:384](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L484) +- [apiWebdavProperties1/copyFile.feature:453](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L453) +- [apiWebdavProperties1/copyFile.feature:454](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L454) +- [apiWebdavProperties1/copyFile.feature:479](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L479) +- [apiWebdavProperties1/copyFile.feature:480](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L480) #### [downloading an old version of a file returns 501](https://github.com/owncloud/ocis/issues/2261) - [apiVersions/fileVersions.feature:437](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersions.feature#L437) diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index e117a717577..8cdc68d33c5 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -28,18 +28,17 @@ Basic file management like up and download, move, copy, properties, quota, trash - [apiVersions/fileVersionAuthor.feature:220](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L220) #### [Getting information about a folder overwritten by a file gives 500 error instead of 404](https://github.com/owncloud/ocis/issues/1239) -- [apiWebdavProperties1/copyFile.feature:226](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L226) -- [apiWebdavProperties1/copyFile.feature:227](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L227) -- [apiWebdavProperties1/copyFile.feature:244](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L244) -- [apiWebdavProperties1/copyFile.feature:245](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L245) - +- [apiWebdavProperties1/copyFile.feature:287](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L287) +- [apiWebdavProperties1/copyFile.feature:288](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L288) +- [apiWebdavProperties1/copyFile.feature:310](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L310) +- [apiWebdavProperties1/copyFile.feature:311](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L311) #### [Custom dav properties with namespaces are rendered incorrectly](https://github.com/owncloud/ocis/issues/2140) _ocdav: double check the webdav property parsing when custom namespaces are used_ -- [apiWebdavProperties1/setFileProperties.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L32) -- [apiWebdavProperties1/setFileProperties.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L33) -- [apiWebdavProperties1/setFileProperties.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L63) -- [apiWebdavProperties1/setFileProperties.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L64) +- [apiWebdavProperties1/setFileProperties.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L37) +- [apiWebdavProperties1/setFileProperties.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L38) +- [apiWebdavProperties1/setFileProperties.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L78) +- [apiWebdavProperties1/setFileProperties.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L79) #### [Cannot set custom webDav properties](https://github.com/owncloud/product/issues/264) - [apiWebdavProperties2/getFileProperties.feature:348](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L348) @@ -119,12 +118,12 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks/publicLink.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L35) - [apiWebdavLocks/publicLink.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L49) - [apiWebdavLocks/publicLink.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L50) -- [apiWebdavLocks/publicLink.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L62) -- [apiWebdavLocks/publicLink.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L63) -- [apiWebdavLocks/publicLink.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L77) -- [apiWebdavLocks/publicLink.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L78) -- [apiWebdavLocks/publicLink.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L97) -- [apiWebdavLocks/publicLink.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L98) +- [apiWebdavLocks/publicLink.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L67) +- [apiWebdavLocks/publicLink.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L68) +- [apiWebdavLocks/publicLink.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L87) +- [apiWebdavLocks/publicLink.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L88) +- [apiWebdavLocks/publicLink.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L107) +- [apiWebdavLocks/publicLink.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L108) - [apiWebdavLocks/publicLinkLockdiscovery.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L22) - [apiWebdavLocks/publicLinkLockdiscovery.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L23) - [apiWebdavLocks/publicLinkLockdiscovery.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L37) @@ -538,20 +537,20 @@ _requires a [CS3 user provisioning api that can update the quota for a user](htt - [apiMain/quota.feature:289](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/quota.feature#L289) Scenario Outline: Retrieving folder quota of shared folder with quota when no quota is set for recipient -- [apiWebdavProperties1/getQuota.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L48) -- [apiWebdavProperties1/getQuota.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L49) +- [apiWebdavProperties1/getQuota.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L58) +- [apiWebdavProperties1/getQuota.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L59) Scenario Outline: Retrieving folder quota when quota is set and a file was uploaded -- [apiWebdavProperties1/getQuota.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L61) -- [apiWebdavProperties1/getQuota.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L62) -Scenario Outline: Retrieving folder quota when quota is set and a file was received - [apiWebdavProperties1/getQuota.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L77) - [apiWebdavProperties1/getQuota.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L78) +Scenario Outline: Retrieving folder quota when quota is set and a file was received +- [apiWebdavProperties1/getQuota.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L98) +- [apiWebdavProperties1/getQuota.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L99) Scenario Outline: Retrieving folder quota when no quota is set - [apiWebdavProperties1/getQuota.feature:17](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L17) - [apiWebdavProperties1/getQuota.feature:18](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L18) Scenario Outline: Retrieving folder quota when quota is set -- [apiWebdavProperties1/getQuota.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L27) -- [apiWebdavProperties1/getQuota.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L28) +- [apiWebdavProperties1/getQuota.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L32) +- [apiWebdavProperties1/getQuota.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L33) #### [Private link support](https://github.com/owncloud/product/issues/201) #### [oc:privatelink property not returned in webdav responses](https://github.com/owncloud/product/issues/262) @@ -1072,10 +1071,10 @@ Scenario Outline: A disabled user cannot use webdav - [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L61) - [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L78) - [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L79) -- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L26) -- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L27) -- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L87) -- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L88) +- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L27) +- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L28) +- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L91) +- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L92) - [apiMain/caldav.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/caldav.feature#L8) - [apiMain/caldav.feature:15](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/caldav.feature#L15) @@ -1119,10 +1118,10 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers - [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L72) #### [Blacklist files extensions](https://github.com/owncloud/ocis/issues/2177) -- [apiWebdavProperties1/copyFile.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L107) -- [apiWebdavProperties1/copyFile.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L108) -- [apiWebdavProperties1/createFolder.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L71) -- [apiWebdavProperties1/createFolder.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L72) +- [apiWebdavProperties1/copyFile.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L133) +- [apiWebdavProperties1/copyFile.feature:134](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L134) +- [apiWebdavProperties1/createFolder.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L92) +- [apiWebdavProperties1/createFolder.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L93) - [apiWebdavUpload1/uploadFile.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L132) - [apiWebdavUpload1/uploadFile.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L133) - [apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature#L19) @@ -1177,44 +1176,44 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers - [apiShareManagementToShares/mergeShare.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/mergeShare.feature#L89) #### Additional shares to the same resource (e.g. user and group share) are now auto-accepted with the existing mountpoint -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:553](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:553) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:554](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:554) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:575](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:575) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:576](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:576) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:553](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L553) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:554](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L554) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:575](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L575) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:576](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L576) ### To triage _The below features have been added after I last categorized them. AFAICT they are bugs. @jfd_ #### [PATCH request for TUS upload with wrong checksum gives incorrect response](https://github.com/owncloud/ocis/issues/1755) -- [apiWebdavUploadTUS/checksums.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L65) -- [apiWebdavUploadTUS/checksums.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L66) -- [apiWebdavUploadTUS/checksums.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L67) -- [apiWebdavUploadTUS/checksums.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L68) -- [apiWebdavUploadTUS/checksums.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L131) -- [apiWebdavUploadTUS/checksums.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L132) -- [apiWebdavUploadTUS/checksums.feature:172](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L172) -- [apiWebdavUploadTUS/checksums.feature:173](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L173) -- [apiWebdavUploadTUS/checksums.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L174) -- [apiWebdavUploadTUS/checksums.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L175) -- [apiWebdavUploadTUS/checksums.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L215) -- [apiWebdavUploadTUS/checksums.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L216) -- [apiWebdavUploadTUS/checksums.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L217) -- [apiWebdavUploadTUS/checksums.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L218) +- [apiWebdavUploadTUS/checksums.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L81) +- [apiWebdavUploadTUS/checksums.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L82) +- [apiWebdavUploadTUS/checksums.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L83) +- [apiWebdavUploadTUS/checksums.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L84) +- [apiWebdavUploadTUS/checksums.feature:168](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L168) +- [apiWebdavUploadTUS/checksums.feature:169](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L169) +- [apiWebdavUploadTUS/checksums.feature:219](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L219) +- [apiWebdavUploadTUS/checksums.feature:220](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L220) +- [apiWebdavUploadTUS/checksums.feature:221](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L221) +- [apiWebdavUploadTUS/checksums.feature:222](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L222) +- [apiWebdavUploadTUS/checksums.feature:274](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L274) +- [apiWebdavUploadTUS/checksums.feature:275](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L275) +- [apiWebdavUploadTUS/checksums.feature:276](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L276) +- [apiWebdavUploadTUS/checksums.feature:277](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L277) - [apiWebdavUploadTUS/optionsRequest.feature:7](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L7) -- [apiWebdavUploadTUS/optionsRequest.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L20) -- [apiWebdavUploadTUS/optionsRequest.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L33) -- [apiWebdavUploadTUS/optionsRequest.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L46) -- [apiWebdavUploadTUS/uploadToShare.feature:173](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L173) -- [apiWebdavUploadTUS/uploadToShare.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L174) -- [apiWebdavUploadTUS/uploadToShare.feature:192](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L192) -- [apiWebdavUploadTUS/uploadToShare.feature:193](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L193) -- [apiWebdavUploadTUS/uploadToShare.feature:211](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L211) -- [apiWebdavUploadTUS/uploadToShare.feature:212](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L212) -- [apiWebdavUploadTUS/uploadToShare.feature:248](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L248) -- [apiWebdavUploadTUS/uploadToShare.feature:249](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L249) -- [apiWebdavUploadTUS/uploadToShare.feature:289](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L289) -- [apiWebdavUploadTUS/uploadToShare.feature:290](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L290) +- [apiWebdavUploadTUS/optionsRequest.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L31) +- [apiWebdavUploadTUS/optionsRequest.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L55) +- [apiWebdavUploadTUS/optionsRequest.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L79) +- [apiWebdavUploadTUS/uploadToShare.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L216) +- [apiWebdavUploadTUS/uploadToShare.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L217) +- [apiWebdavUploadTUS/uploadToShare.feature:240](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L240) +- [apiWebdavUploadTUS/uploadToShare.feature:241](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L241) +- [apiWebdavUploadTUS/uploadToShare.feature:264](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L264) +- [apiWebdavUploadTUS/uploadToShare.feature:265](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L265) +- [apiWebdavUploadTUS/uploadToShare.feature:310](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L310) +- [apiWebdavUploadTUS/uploadToShare.feature:311](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L311) +- [apiWebdavUploadTUS/uploadToShare.feature:361](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L361) +- [apiWebdavUploadTUS/uploadToShare.feature:362](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L362) #### [Share inaccessible if folder with same name was deleted and recreated](https://github.com/owncloud/ocis/issues/1787) - [apiShareReshareToShares1/reShare.feature:269](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares1/reShare.feature#L269) @@ -1272,10 +1271,10 @@ _ocs: api compatibility, return correct status code_ - [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:455](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L455) #### [Trying to copy a file into a readonly share gives HTTP 500 error](https://github.com/owncloud/ocis/issues/2166) -- [apiWebdavProperties1/copyFile.feature:362](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L362) -- [apiWebdavProperties1/copyFile.feature:363](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L363) -- [apiWebdavProperties1/copyFile.feature:383](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L483) -- [apiWebdavProperties1/copyFile.feature:384](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L484) +- [apiWebdavProperties1/copyFile.feature:453](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L453) +- [apiWebdavProperties1/copyFile.feature:454](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L454) +- [apiWebdavProperties1/copyFile.feature:479](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L479) +- [apiWebdavProperties1/copyFile.feature:480](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L480) ### [Allow public link sharing only for certain groups feature not implemented] - [apiSharePublicLink2/allowGroupToCreatePublicLinks.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/allowGroupToCreatePublicLinks.feature#L35) From fa24ac39607a001fc50b7fe54b9dd808731da48b Mon Sep 17 00:00:00 2001 From: kobergj Date: Fri, 4 Feb 2022 10:49:29 +0100 Subject: [PATCH 33/49] Restoring Spaces (#2458) * add restore functionality to decomposedfs Signed-off-by: jkoberg * add changelog item Signed-off-by: jkoberg * find trashed spaces Signed-off-by: jkoberg * add includeTrashed paramter to ListSpaces call Signed-off-by: jkoberg * only add * if neccessary Signed-off-by: jkoberg * move instead delete to make restore possible Signed-off-by: jkoberg * pass trashed information via Opaque Signed-off-by: jkoberg * don't return trashed space for non owners Signed-off-by: jkoberg * revert includeTrashed hack Signed-off-by: jkoberg * no need to symlink trashed spaces any more Signed-off-by: jkoberg * bad solution - just wanna paint it green Signed-off-by: jkoberg * allow listing of deleted spaces Signed-off-by: jkoberg --- changelog/unreleased/restore-spaces.md | 7 + .../grpc/services/gateway/storageprovider.go | 11 +- pkg/storage/utils/decomposedfs/spaces.go | 137 +++++++++++++----- 3 files changed, 116 insertions(+), 39 deletions(-) create mode 100644 changelog/unreleased/restore-spaces.md diff --git a/changelog/unreleased/restore-spaces.md b/changelog/unreleased/restore-spaces.md new file mode 100644 index 00000000000..93d288b02d0 --- /dev/null +++ b/changelog/unreleased/restore-spaces.md @@ -0,0 +1,7 @@ +Enhancement: Restore spaces that were previously deleted + +After the first step of the two step delete process an admin can decide to restore +the space instead of deleting it. This will undo the deletion and +all files and shares are accessible again + +https://github.com/cs3org/reva/pull/2457 diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index 6aebda5f5a2..b2337d64f1b 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -286,9 +286,11 @@ func (s *svc) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorag }, nil } - id := res.StorageSpace.Root - s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), id) - s.cache.RemoveListStorageProviders(id) + if res.Status.Code == rpc.Code_CODE_OK { + id := res.StorageSpace.Root + s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), id) + s.cache.RemoveListStorageProviders(id) + } return res, nil } @@ -1089,8 +1091,9 @@ func (s *svc) findSpaces(ctx context.Context, ref *provider.Reference) ([]*regis } listReq := ®istry.ListStorageProvidersRequest{ - Opaque: &typesv1beta1.Opaque{}, + Opaque: &typesv1beta1.Opaque{Map: make(map[string]*typesv1beta1.OpaqueEntry)}, } + sdk.EncodeOpaqueMap(listReq.Opaque, filters) return s.findProvider(ctx, listReq) diff --git a/pkg/storage/utils/decomposedfs/spaces.go b/pkg/storage/utils/decomposedfs/spaces.go index a380eedc6eb..422c27c0175 100644 --- a/pkg/storage/utils/decomposedfs/spaces.go +++ b/pkg/storage/utils/decomposedfs/spaces.go @@ -27,6 +27,7 @@ import ( "path/filepath" "strconv" "strings" + "time" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" permissionsv1beta1 "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" @@ -217,7 +218,8 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide matches := []string{} for _, spaceType := range spaceTypes { - m, err := filepath.Glob(filepath.Join(fs.o.Root, "spaces", spaceType, nodeID)) + path := filepath.Join(fs.o.Root, "spaces", spaceType, nodeID) + m, err := filepath.Glob(path) if err != nil { return nil, err } @@ -317,10 +319,50 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide // UpdateStorageSpace updates a storage space func (fs *Decomposedfs) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) { - space := req.StorageSpace + var restore bool + if req.Opaque != nil { + _, restore = req.Opaque.Map["restore"] + } + space := req.StorageSpace _, spaceID, _ := utils.SplitStorageSpaceID(space.Id.OpaqueId) + if restore { + matches, err := filepath.Glob(filepath.Join(fs.o.Root, "spaces", spaceTypeAny, spaceID)) + if err != nil { + return nil, err + } + + if len(matches) != 1 { + return &provider.UpdateStorageSpaceResponse{ + Status: &v1beta11.Status{ + Code: v1beta11.Code_CODE_NOT_FOUND, + Message: fmt.Sprintf("restoring space failed: found %d matching spaces", len(matches)), + }, + }, nil + + } + + target, err := os.Readlink(matches[0]) + if err != nil { + appctx.GetLogger(ctx).Error().Err(err).Str("match", matches[0]).Msg("could not read link, skipping") + } + + n, err := node.ReadNode(ctx, fs.lu, filepath.Base(target)) + if err != nil { + return nil, err + } + + newnode := *n + newnode.Name = strings.Split(n.Name, node.TrashIDDelimiter)[0] + newnode.Exists = false + + err = fs.tp.Move(ctx, n, &newnode) + if err != nil { + return nil, err + } + } + matches, err := filepath.Glob(filepath.Join(fs.o.Root, "spaces", spaceTypeAny, spaceID)) if err != nil { return nil, err @@ -377,8 +419,29 @@ func (fs *Decomposedfs) DeleteStorageSpace(ctx context.Context, req *provider.De _, purge = opaque.Map["purge"] } + spaceID := req.Id.OpaqueId + + matches, err := filepath.Glob(filepath.Join(fs.o.Root, "spaces", spaceTypeAny, spaceID)) + if err != nil { + return err + } + + if len(matches) != 1 { + return fmt.Errorf("delete space failed: found %d matching spaces", len(matches)) + } + + target, err := os.Readlink(matches[0]) + if err != nil { + appctx.GetLogger(ctx).Error().Err(err).Str("match", matches[0]).Msg("could not read link, skipping") + } + + n, err := node.ReadNode(ctx, fs.lu, filepath.Base(target)) + if err != nil { + return err + } + if purge { - if !strings.Contains(req.Id.OpaqueId, node.TrashIDDelimiter) { + if !strings.Contains(n.Name, node.TrashIDDelimiter) { return errtypes.NewErrtypeFromStatus(status.NewInvalidArg(ctx, "can't purge enabled space")) } ip := fs.lu.InternalPath(req.Id.OpaqueId) @@ -400,31 +463,27 @@ func (fs *Decomposedfs) DeleteStorageSpace(ctx context.Context, req *provider.De return fmt.Errorf("delete space failed: found %d matching spaces", len(matches)) } - return os.RemoveAll(matches[0]) - } - - spaceID := req.Id.OpaqueId - - matches, err := filepath.Glob(filepath.Join(fs.o.Root, "spaces", spaceTypeAny, spaceID)) - if err != nil { - return err - } + if err := os.RemoveAll(matches[0]); err != nil { + return err + } - if len(matches) != 1 { - return fmt.Errorf("delete space failed: found %d matching spaces", len(matches)) - } + matches, err = filepath.Glob(filepath.Join(fs.o.Root, "nodes", "root", req.Id.OpaqueId+node.TrashIDDelimiter+"*")) + if err != nil { + return err + } - target, err := os.Readlink(matches[0]) - if err != nil { - appctx.GetLogger(ctx).Error().Err(err).Str("match", matches[0]).Msg("could not read link, skipping") - } + if len(matches) != 1 { + return fmt.Errorf("delete root node failed: found %d matching root nodes", len(matches)) + } - n, err := node.ReadNode(ctx, fs.lu, filepath.Base(target)) - if err != nil { - return err + return os.RemoveAll(matches[0]) } - - err = fs.tp.Delete(ctx, n) + // don't delete - just rename + dn := *n + deletionTime := time.Now().UTC().Format(time.RFC3339Nano) + dn.Name = n.Name + node.TrashIDDelimiter + deletionTime + dn.Exists = false + err = fs.tp.Move(ctx, n, &dn) if err != nil { return err } @@ -434,15 +493,9 @@ func (fs *Decomposedfs) DeleteStorageSpace(ctx context.Context, req *provider.De return err } - trashPathMatches, err := filepath.Glob(n.InternalPath() + node.TrashIDDelimiter + "*") - if err != nil { - return err - } - if len(trashPathMatches) != 1 { - return fmt.Errorf("delete space failed: found %d matching trashed spaces", len(trashPathMatches)) - } - trashPath := trashPathMatches[0] - return os.Symlink(trashPath, filepath.Join(filepath.Dir(matches[0]), filepath.Base(trashPath))) + trashPath := dn.InternalPath() + np := filepath.Join(filepath.Dir(matches[0]), filepath.Base(trashPath)) + return os.Symlink(trashPath, np) } // createHiddenSpaceFolder bootstraps a storage space root with a hidden ".space" folder used to store space related @@ -487,8 +540,15 @@ func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, if err != nil || !ok { return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to Stat the space %s", user.Username, n.SpaceRoot.ID)) } - if n.SpaceRoot != nil && strings.Contains(n.SpaceRoot.ID, node.TrashIDDelimiter) { - return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to list deleted spaces %s", user.Username, n.SpaceRoot.ID)) + + if strings.Contains(n.Name, node.TrashIDDelimiter) { + ok, err := node.NewPermissions(fs.lu).HasPermission(ctx, n, func(p *provider.ResourcePermissions) bool { + // TODO: Which permission do I need to see the space? + return p.AddGrant + }) + if err != nil || !ok { + return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to list deleted spaces %s", user.Username, n.SpaceRoot.ID)) + } } } @@ -563,6 +623,13 @@ func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, // Mtime is set either as node.tmtime or as fi.mtime below } + if strings.Contains(n.Name, node.TrashIDDelimiter) { + space.Opaque.Map["trashed"] = &types.OpaqueEntry{ + Decoder: "plain", + Value: []byte("trashed"), + } + } + space.Owner = &userv1beta1.User{ // FIXME only return a UserID, not a full blown user object Id: owner, } From 6f3572e27d782dcc69b7f3be70490023bb44a8ac Mon Sep 17 00:00:00 2001 From: Willy Kloucek <34452982+wkloucek@users.noreply.github.com> Date: Fri, 4 Feb 2022 12:48:23 +0100 Subject: [PATCH 34/49] unprotected ocs config endpoint (#2503) * remove protection from ocs config endpoint * remove passing ocs config test from expected failures --- changelog/unreleased/fix-ocs-config-unprotected.md | 8 ++++++++ internal/http/services/owncloud/ocs/ocs.go | 5 ++++- tests/acceptance/expected-failures-on-EOS-storage.md | 2 -- tests/acceptance/expected-failures-on-OCIS-storage.md | 5 +---- tests/acceptance/expected-failures-on-S3NG-storage.md | 5 +---- 5 files changed, 14 insertions(+), 11 deletions(-) create mode 100644 changelog/unreleased/fix-ocs-config-unprotected.md diff --git a/changelog/unreleased/fix-ocs-config-unprotected.md b/changelog/unreleased/fix-ocs-config-unprotected.md new file mode 100644 index 00000000000..edb8e480be7 --- /dev/null +++ b/changelog/unreleased/fix-ocs-config-unprotected.md @@ -0,0 +1,8 @@ +Bugfix: Remove the protection from /v?.php/config endpoints + +We've removed the protection from the "/v1.php/config" and "/v2.php/config" endpoints +to be API compatible with ownCloud 10. + +https://github.com/cs3org/reva/issues/2503 +https://github.com/owncloud/ocis/issues/1338 + diff --git a/internal/http/services/owncloud/ocs/ocs.go b/internal/http/services/owncloud/ocs/ocs.go index 5dc87fff9f4..702358b9786 100644 --- a/internal/http/services/owncloud/ocs/ocs.go +++ b/internal/http/services/owncloud/ocs/ocs.go @@ -84,7 +84,10 @@ func (s *svc) Close() error { } func (s *svc) Unprotected() []string { - return []string{} + return []string{ + "/v1.php/config", + "/v2.php/config", + } } func (s *svc) routerInit() error { diff --git a/tests/acceptance/expected-failures-on-EOS-storage.md b/tests/acceptance/expected-failures-on-EOS-storage.md index c7efaa868c0..e5c1733b665 100644 --- a/tests/acceptance/expected-failures-on-EOS-storage.md +++ b/tests/acceptance/expected-failures-on-EOS-storage.md @@ -68,11 +68,9 @@ ### [no command equivalent to occ](https://github.com/owncloud/ocis/issues/1317) - [apiMain/status.feature:5](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/status.feature#L5) -### [ocs config endpoint only accessible by authorized users](https://github.com/owncloud/ocis/issues/1338) ### [HTTP 401 Unauthorized responses don't contain a body](https://github.com/owncloud/ocis/issues/1337) - [apiAuthOcs/ocsDELETEAuth.feature:9](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsDELETEAuth.feature#L9) - [apiAuthOcs/ocsGETAuth.feature:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L10) -- [apiAuthOcs/ocsGETAuth.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L33) - [apiAuthOcs/ocsGETAuth.feature:53](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L53) - [apiAuthOcs/ocsGETAuth.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L88) - [apiAuthOcs/ocsGETAuth.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L121) diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index 49630049afa..f22f4784782 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -828,12 +828,10 @@ API, search, favorites, config, capabilities, not existing endpoints, CORS and o #### [Different version, edition and productname in status request and capabilities request](https://github.com/owncloud/ocis/issues/2174) - [apiMain/status.feature:5](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/status.feature#L5) Scenario: Status.php is correct -#### [ocs config endpoint only accessible by authorized users](https://github.com/owncloud/ocis/issues/1338) #### [Ability to return error messages in Webdav response bodies](https://github.com/owncloud/ocis/issues/1293) - [apiAuthOcs/ocsDELETEAuth.feature:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsDELETEAuth.feature#L10) Scenario: send DELETE requests to OCS endpoints as admin with wrong password - [apiAuthOcs/ocsGETAuth.feature:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L10) Scenario: using OCS anonymously -- [apiAuthOcs/ocsGETAuth.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L33) Scenario: ocs config end point accessible by unauthorized users - [apiAuthOcs/ocsGETAuth.feature:53](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L53) Scenario: using OCS with non-admin basic auth - [apiAuthOcs/ocsGETAuth.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L88) Scenario: using OCS as normal user with wrong password - [apiAuthOcs/ocsGETAuth.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L121) Scenario:using OCS with admin basic auth @@ -1045,7 +1043,7 @@ Scenario Outline: A disabled user cannot use webdav - [apiCapabilities/capabilities.feature:737](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L737) - [apiCapabilities/capabilities.feature:766](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L766) - [apiCapabilities/capabilities.feature:795](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L795) -- [apiCapabilities/capabilities.feature:827](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L827) +- [apiCapabilities/capabilities.feature:827](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L827) - [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L25) - [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L26) - [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L44) @@ -1309,4 +1307,3 @@ _ocs: api compatibility, return correct status code_ Note: always have an empty line at the end of this file. The bash script that processes this file may not process a scenario reference on the last line. - diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index 8cdc68d33c5..3bd38fb77b8 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -845,12 +845,10 @@ API, search, favorites, config, capabilities, not existing endpoints, CORS and o #### [Different version, edition and productname in status request and capabilities request](https://github.com/owncloud/ocis/issues/2174) - [apiMain/status.feature:5](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/status.feature#L5) Scenario: Status.php is correct -#### [ocs config endpoint only accessible by authorized users](https://github.com/owncloud/ocis/issues/1338) #### [Ability to return error messages in Webdav response bodies](https://github.com/owncloud/ocis/issues/1293) - [apiAuthOcs/ocsDELETEAuth.feature:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsDELETEAuth.feature#L10) Scenario: send DELETE requests to OCS endpoints as admin with wrong password - [apiAuthOcs/ocsGETAuth.feature:10](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L10) Scenario: using OCS anonymously -- [apiAuthOcs/ocsGETAuth.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L33) Scenario: ocs config end point accessible by unauthorized users - [apiAuthOcs/ocsGETAuth.feature:53](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L53) Scenario: using OCS with non-admin basic auth - [apiAuthOcs/ocsGETAuth.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L88) Scenario: using OCS as normal user with wrong password - [apiAuthOcs/ocsGETAuth.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthOcs/ocsGETAuth.feature#L121) Scenario:using OCS with admin basic auth @@ -1062,7 +1060,7 @@ Scenario Outline: A disabled user cannot use webdav - [apiCapabilities/capabilities.feature:737](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L737) - [apiCapabilities/capabilities.feature:766](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L766) - [apiCapabilities/capabilities.feature:795](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L795) -- [apiCapabilities/capabilities.feature:827](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L827) +- [apiCapabilities/capabilities.feature:827](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L827) - [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L25) - [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L26) - [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L44) @@ -1310,4 +1308,3 @@ _ocs: api compatibility, return correct status code_ Note: always have an empty line at the end of this file. The bash script that processes this file may not process a scenario reference on the last line. - From d6a8f9b802df6b11f218c039521d420348eb7eb9 Mon Sep 17 00:00:00 2001 From: kobergj Date: Fri, 4 Feb 2022 12:55:25 +0100 Subject: [PATCH 35/49] [tests-only] Fix panic in storageSpaceFromNode (#2504) * fix panic in storageSpaceFromNode Signed-off-by: jkoberg * check node for being nil Signed-off-by: jkoberg * Revert "check node for being nil" This reverts commit e38228e1d91c7a21c8c1d010020b66740d1ae354. --- pkg/storage/utils/decomposedfs/spaces.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/storage/utils/decomposedfs/spaces.go b/pkg/storage/utils/decomposedfs/spaces.go index 422c27c0175..1a6bcee6b56 100644 --- a/pkg/storage/utils/decomposedfs/spaces.go +++ b/pkg/storage/utils/decomposedfs/spaces.go @@ -538,7 +538,7 @@ func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, return p.Stat }) if err != nil || !ok { - return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to Stat the space %s", user.Username, n.SpaceRoot.ID)) + return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to Stat the space %s", user.Username, n.ID)) } if strings.Contains(n.Name, node.TrashIDDelimiter) { @@ -547,7 +547,7 @@ func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, return p.AddGrant }) if err != nil || !ok { - return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to list deleted spaces %s", user.Username, n.SpaceRoot.ID)) + return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to list deleted spaces %s", user.Username, n.ID)) } } } From 983af9327c2f85d692947d1ec98756ce243c70fe Mon Sep 17 00:00:00 2001 From: David Christofas Date: Fri, 4 Feb 2022 18:29:01 +0100 Subject: [PATCH 36/49] fix propfind listing for files (#2506) --- changelog/unreleased/propfind-listing.md | 6 ++ internal/http/services/owncloud/ocdav/copy.go | 34 +++++------- .../http/services/owncloud/ocdav/net/net.go | 35 ++++++++++++ .../services/owncloud/ocdav/net/net_test.go | 55 +++++++++++++++++++ .../owncloud/ocdav/propfind/propfind.go | 27 +++++---- .../services/owncloud/ocdav/publicfile.go | 14 ++--- .../http/services/owncloud/ocdav/trashbin.go | 23 ++++---- 7 files changed, 139 insertions(+), 55 deletions(-) create mode 100644 changelog/unreleased/propfind-listing.md create mode 100644 internal/http/services/owncloud/ocdav/net/net_test.go diff --git a/changelog/unreleased/propfind-listing.md b/changelog/unreleased/propfind-listing.md new file mode 100644 index 00000000000..168c17773db --- /dev/null +++ b/changelog/unreleased/propfind-listing.md @@ -0,0 +1,6 @@ +Bugfix: Fix propfind listing for files + +When doing a propfind for a file the result contained the files twice. + +https://github.com/owncloud/ocis/issues/3066 +https://github.com/cs3org/reva/pull/2506 diff --git a/internal/http/services/owncloud/ocdav/copy.go b/internal/http/services/owncloud/ocdav/copy.go index f3c94eb355a..843a467f84f 100644 --- a/internal/http/services/owncloud/ocdav/copy.go +++ b/internal/http/services/owncloud/ocdav/copy.go @@ -47,7 +47,7 @@ type copy struct { source *provider.Reference sourceInfo *provider.ResourceInfo destination *provider.Reference - depth string + depth net.Depth successCode int } @@ -105,7 +105,7 @@ func (s *svc) handlePathCopy(w http.ResponseWriter, r *http.Request, ns string) } if err := s.executePathCopy(ctx, client, w, r, cp); err != nil { - sublog.Error().Err(err).Str("depth", cp.depth).Msg("error executing path copy") + sublog.Error().Err(err).Str("depth", cp.depth.String()).Msg("error executing path copy") w.WriteHeader(http.StatusInternalServerError) } w.WriteHeader(cp.successCode) @@ -136,7 +136,7 @@ func (s *svc) executePathCopy(ctx context.Context, client gateway.GatewayAPIClie // TODO: also copy properties: https://tools.ietf.org/html/rfc4918#section-9.8.2 - if cp.depth != "infinity" { + if cp.depth != net.DepthInfinity { return nil } @@ -325,7 +325,7 @@ func (s *svc) handleSpacesCopy(w http.ResponseWriter, r *http.Request, spaceID s err = s.executeSpacesCopy(ctx, w, client, cp) if err != nil { - sublog.Error().Err(err).Str("depth", cp.depth).Msg("error descending directory") + sublog.Error().Err(err).Str("depth", cp.depth.String()).Msg("error descending directory") w.WriteHeader(http.StatusInternalServerError) } w.WriteHeader(cp.successCode) @@ -358,7 +358,7 @@ func (s *svc) executeSpacesCopy(ctx context.Context, w http.ResponseWriter, clie // TODO: also copy properties: https://tools.ietf.org/html/rfc4918#section-9.8.2 - if cp.depth != "infinity" { + if cp.depth != net.DepthInfinity { return nil } @@ -488,16 +488,23 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re errors.HandleWebdavError(log, w, b, err) return nil } - depth, err := extractDepth(w, r) + dh := r.Header.Get(net.HeaderDepth) + depth, err := net.ParseDepth(dh) + if err != nil { w.WriteHeader(http.StatusBadRequest) - m := fmt.Sprintf("Depth header is set to incorrect value %v", depth) + m := fmt.Sprintf("Depth header is set to incorrect value %v", dh) b, err := errors.Marshal(errors.SabredavBadRequest, m, "") errors.HandleWebdavError(log, w, b, err) return nil } + if dh == "" { + // net.ParseDepth returns "1" for an empty value but copy expects "infinity" + // so we overwrite it here + depth = net.DepthInfinity + } - log.Debug().Str("overwrite", overwrite).Str("depth", depth).Msg("copy") + log.Debug().Str("overwrite", overwrite).Str("depth", depth.String()).Msg("copy") client, err := s.getClient() if err != nil { @@ -605,14 +612,3 @@ func extractOverwrite(w http.ResponseWriter, r *http.Request) (string, error) { return overwrite, nil } - -func extractDepth(w http.ResponseWriter, r *http.Request) (string, error) { - depth := r.Header.Get(net.HeaderDepth) - if depth == "" { - depth = "infinity" - } - if depth != "infinity" && depth != "0" { - return "", errInvalidValue - } - return depth, nil -} diff --git a/internal/http/services/owncloud/ocdav/net/net.go b/internal/http/services/owncloud/ocdav/net/net.go index e5f3ded8dc5..06472d607da 100644 --- a/internal/http/services/owncloud/ocdav/net/net.go +++ b/internal/http/services/owncloud/ocdav/net/net.go @@ -44,8 +44,23 @@ const ( PropQuotaUnknown = "-2" // PropOcFavorite is the favorite ns property PropOcFavorite = "http://owncloud.org/ns/favorite" + + // DepthZero represents the webdav zero depth value + DepthZero Depth = "0" + // DepthOne represents the webdav one depth value + DepthOne Depth = "1" + // DepthInfinity represents the webdav infinity depth value + DepthInfinity Depth = "infinity" ) +// Depth is a type representing the webdav depth header value +type Depth string + +// String returns the string representation of the webdav depth value +func (d Depth) String() string { + return string(d) +} + // replaceAllStringSubmatchFunc is taken from 'Go: Replace String with Regular Expression Callback' // see: https://elliotchance.medium.com/go-replace-string-with-regular-expression-callback-f89948bad0bb func replaceAllStringSubmatchFunc(re *regexp.Regexp, str string, repl func([]string) string) string { @@ -78,3 +93,23 @@ func EncodePath(path string) string { return sb.String() }) } + +// ParseDepth parses the depth header value defined in https://tools.ietf.org/html/rfc4918#section-9.1 +// Valid values are "0", "1" and "infinity". An empty string will be parsed to "1". +// For all other values this method returns an error. +func ParseDepth(s string) (Depth, error) { + if s == "" { + return DepthOne, nil + } + + switch strings.ToLower(s) { + case DepthZero.String(): + return DepthZero, nil + case DepthOne.String(): + return DepthOne, nil + case DepthInfinity.String(): + return DepthInfinity, nil + default: + return "", fmt.Errorf("invalid depth: %s", s) + } +} diff --git a/internal/http/services/owncloud/ocdav/net/net_test.go b/internal/http/services/owncloud/ocdav/net/net_test.go new file mode 100644 index 00000000000..2b0e2c32aaa --- /dev/null +++ b/internal/http/services/owncloud/ocdav/net/net_test.go @@ -0,0 +1,55 @@ +// Copyright 2018-2022 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package net + +import "testing" + +func TestParseDepth(t *testing.T) { + tests := map[string]Depth{ + "": DepthOne, + "0": DepthZero, + "1": DepthOne, + "infinity": DepthInfinity, + } + + for input, expected := range tests { + parsed, err := ParseDepth(input) + if err != nil { + t.Errorf("failed to parse depth %s", input) + } + if parsed != expected { + t.Errorf("parseDepth returned %s expected %s", parsed.String(), expected.String()) + } + } + + _, err := ParseDepth("invalid") + if err == nil { + t.Error("parse depth didn't return an error for invalid depth: invalid") + } +} + +var result Depth + +func BenchmarkParseDepth(b *testing.B) { + inputs := []string{"", "0", "1", "infinity", "INFINITY"} + size := len(inputs) + for i := 0; i < b.N; i++ { + result, _ = ParseDepth(inputs[i%size]) + } +} diff --git a/internal/http/services/owncloud/ocdav/propfind/propfind.go b/internal/http/services/owncloud/ocdav/propfind/propfind.go index 96f6aab6bf3..2af6777e9f7 100644 --- a/internal/http/services/owncloud/ocdav/propfind/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind/propfind.go @@ -237,15 +237,12 @@ func (p *Handler) statSpace(ctx context.Context, client gateway.GatewayAPIClient } func (p *Handler) getResourceInfos(ctx context.Context, w http.ResponseWriter, r *http.Request, pf XML, spaces []*provider.StorageSpace, requestPath string, spacesPropfind bool, log zerolog.Logger) ([]*provider.ResourceInfo, bool, bool) { - depth := r.Header.Get(net.HeaderDepth) - if depth == "" { - depth = "1" - } - // see https://tools.ietf.org/html/rfc4918#section-9.1 - if depth != "0" && depth != "1" && depth != "infinity" { - log.Debug().Str("depth", depth).Msg("invalid Depth header value") + dh := r.Header.Get(net.HeaderDepth) + depth, err := net.ParseDepth(dh) + if err != nil { + log.Debug().Str("depth", dh).Msg(err.Error()) w.WriteHeader(http.StatusBadRequest) - m := fmt.Sprintf("Invalid Depth header value: %v", depth) + m := fmt.Sprintf("Invalid Depth header value: %v", dh) b, err := errors.Marshal(errors.SabredavBadRequest, m, "") errors.HandleWebdavError(&log, w, b, err) return nil, false, false @@ -355,7 +352,7 @@ func (p *Handler) getResourceInfos(ctx context.Context, w http.ResponseWriter, r // then add children for _, spaceInfo := range spaceInfos { switch { - case !spacesPropfind && spaceInfo.Type != provider.ResourceType_RESOURCE_TYPE_CONTAINER && depth != "infinity": + case !spacesPropfind && spaceInfo.Type != provider.ResourceType_RESOURCE_TYPE_CONTAINER && depth != net.DepthInfinity: // The propfind is requested for a file that exists childPath := strings.TrimPrefix(spaceInfo.Path, requestPath) @@ -375,7 +372,7 @@ func (p *Handler) getResourceInfos(ctx context.Context, w http.ResponseWriter, r childInfos[childName] = spaceInfo } - case spaceInfo.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER && depth == "1": + case spaceInfo.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER && depth == net.DepthOne: switch { case strings.HasPrefix(requestPath, spaceInfo.Path): req := &provider.ListContainerRequest{ @@ -423,7 +420,7 @@ func (p *Handler) getResourceInfos(ctx context.Context, w http.ResponseWriter, r log.Debug().Msg("unhandled") } - case depth == "infinity": + case depth == net.DepthInfinity: // use a stack to explore sub-containers breadth-first if spaceInfo != rootInfo { resourceInfos = append(resourceInfos, spaceInfo) @@ -472,9 +469,11 @@ func (p *Handler) getResourceInfos(ctx context.Context, w http.ResponseWriter, r } } - // now add all aggregated child infos - for _, childInfo := range childInfos { - resourceInfos = append(resourceInfos, childInfo) + if rootInfo.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER { + // now add all aggregated child infos + for _, childInfo := range childInfos { + resourceInfos = append(resourceInfos, childInfo) + } } sendTusHeaders := true diff --git a/internal/http/services/owncloud/ocdav/publicfile.go b/internal/http/services/owncloud/ocdav/publicfile.go index e64ceb92995..512a9e39ba8 100644 --- a/internal/http/services/owncloud/ocdav/publicfile.go +++ b/internal/http/services/owncloud/ocdav/publicfile.go @@ -92,14 +92,10 @@ func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns s sublog := appctx.GetLogger(ctx).With().Interface("tokenStatInfo", tokenStatInfo).Logger() sublog.Debug().Msg("handlePropfindOnToken") - depth := r.Header.Get(net.HeaderDepth) - if depth == "" { - depth = "1" - } - - // see https://tools.ietf.org/html/rfc4918#section-10.2 - if depth != "0" && depth != "1" && depth != "infinity" { - sublog.Debug().Msgf("invalid Depth header value %s", depth) + dh := r.Header.Get(net.HeaderDepth) + depth, err := net.ParseDepth(dh) + if err != nil { + sublog.Debug().Msg(err.Error()) w.WriteHeader(http.StatusBadRequest) return } @@ -114,7 +110,7 @@ func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns s // prefix tokenStatInfo.Path with token tokenStatInfo.Path = filepath.Join(r.URL.Path, tokenStatInfo.Path) - infos := s.getPublicFileInfos(onContainer, depth == "0", tokenStatInfo) + infos := s.getPublicFileInfos(onContainer, depth == net.DepthZero, tokenStatInfo) propRes, err := propfind.MultistatusResponse(ctx, &pf, infos, s.c.PublicURL, ns, nil) if err != nil { diff --git a/internal/http/services/owncloud/ocdav/trashbin.go b/internal/http/services/owncloud/ocdav/trashbin.go index dd58d7b22a3..49470f73811 100644 --- a/internal/http/services/owncloud/ocdav/trashbin.go +++ b/internal/http/services/owncloud/ocdav/trashbin.go @@ -170,12 +170,16 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s ctx, span := rtrace.Provider.Tracer("trash-bin").Start(r.Context(), "list_trashbin") defer span.End() - depth := r.Header.Get(net.HeaderDepth) - if depth == "" { - depth = "1" + sublog := appctx.GetLogger(ctx).With().Logger() + + dh := r.Header.Get(net.HeaderDepth) + depth, err := net.ParseDepth(dh) + if err != nil { + sublog.Debug().Str("depth", dh).Msg(err.Error()) + w.WriteHeader(http.StatusBadRequest) + return } - sublog := appctx.GetLogger(ctx).With().Logger() client, err := s.getClient() if err != nil { sublog.Error().Err(err).Msg("error getting grpc client") @@ -183,14 +187,7 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s return } - // see https://tools.ietf.org/html/rfc4918#section-9.1 - if depth != "0" && depth != "1" && depth != "infinity" { - sublog.Debug().Str("depth", depth).Msgf("invalid Depth header value") - w.WriteHeader(http.StatusBadRequest) - return - } - - if depth == "0" { + if depth == net.DepthZero { propRes, err := h.formatTrashPropfind(ctx, s, u, nil, nil) if err != nil { sublog.Error().Err(err).Msg("error formatting propfind") @@ -253,7 +250,7 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s items := getRecycleRes.RecycleItems - if depth == "infinity" { + if depth == net.DepthInfinity { var stack []string // check sub-containers in reverse order and add them to the stack // the reversed order here will produce a more logical sorting of results From 325f1b12b1d1c82953563599bc6b2301955b7de6 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Mon, 7 Feb 2022 12:16:34 +0545 Subject: [PATCH 37/49] bump CORE_COMMITID to use new getPersonalSpaceIdForUser code in API tests (#2505) --- .drone.env | 2 +- .drone.star | 4 +- .../expected-failures-on-OCIS-storage.md | 430 +++++++++++------ .../expected-failures-on-S3NG-storage.md | 440 ++++++++++++------ 4 files changed, 588 insertions(+), 288 deletions(-) diff --git a/.drone.env b/.drone.env index 51b94a70897..6d8169ead2b 100644 --- a/.drone.env +++ b/.drone.env @@ -1,3 +1,3 @@ # The test runner source for API tests -CORE_COMMITID=2880ab5d326b86336bff29e6709adf774a317d88 +CORE_COMMITID=f4773164f276ce9307bedb1d24fa06c0023b7ad3 CORE_BRANCH=master diff --git a/.drone.star b/.drone.star index d7aaffc2428..b192b330769 100644 --- a/.drone.star +++ b/.drone.star @@ -820,7 +820,7 @@ def ocisIntegrationTests(parallelRuns, skipExceptParts = []): "REVA_LDAP_HOSTNAME": "ldap", "TEST_REVA": "true", "SEND_SCENARIO_LINE_REFERENCES": "true", - "BEHAT_FILTER_TAGS": "~@notToImplementOnOCIS&&~@toImplementOnOCIS&&~comments-app-required&&~@federation-app-required&&~@notifications-app-required&&~systemtags-app-required&&~@provisioning_api-app-required&&~@preview-extension-required&&~@local_storage&&~@skipOnOcis-OCIS-Storage&&~@skipOnOcis&&~@personalSpace&&~@issue-ocis-3023", + "BEHAT_FILTER_TAGS": "~@notToImplementOnOCIS&&~@toImplementOnOCIS&&~comments-app-required&&~@federation-app-required&&~@notifications-app-required&&~systemtags-app-required&&~@provisioning_api-app-required&&~@preview-extension-required&&~@local_storage&&~@skipOnOcis-OCIS-Storage&&~@skipOnOcis&&~@issue-ocis-3023", "DIVIDE_INTO_NUM_PARTS": parallelRuns, "RUN_PART": runPart, "EXPECTED_FAILURES_FILE": "/drone/src/tests/acceptance/expected-failures-on-OCIS-storage.md", @@ -897,7 +897,7 @@ def s3ngIntegrationTests(parallelRuns, skipExceptParts = []): "REVA_LDAP_HOSTNAME": "ldap", "TEST_REVA": "true", "SEND_SCENARIO_LINE_REFERENCES": "true", - "BEHAT_FILTER_TAGS": "~@notToImplementOnOCIS&&~@toImplementOnOCIS&&~comments-app-required&&~@federation-app-required&&~@notifications-app-required&&~systemtags-app-required&&~@provisioning_api-app-required&&~@preview-extension-required&&~@local_storage&&~@skipOnOcis-OCIS-Storage&&~@skipOnOcis&&~@personalSpace&&~@issue-ocis-3023", + "BEHAT_FILTER_TAGS": "~@notToImplementOnOCIS&&~@toImplementOnOCIS&&~comments-app-required&&~@federation-app-required&&~@notifications-app-required&&~systemtags-app-required&&~@provisioning_api-app-required&&~@preview-extension-required&&~@local_storage&&~@skipOnOcis-OCIS-Storage&&~@skipOnOcis&&~@issue-ocis-3023", "DIVIDE_INTO_NUM_PARTS": parallelRuns, "RUN_PART": runPart, "EXPECTED_FAILURES_FILE": "/drone/src/tests/acceptance/expected-failures-on-S3NG-storage.md", diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index f22f4784782..911c952fe91 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -23,20 +23,25 @@ These tests are about overwriting files or folders in the `Shares` folder of a u _ocdav: double check the webdav property parsing when custom namespaces are used_ - [apiWebdavProperties1/setFileProperties.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L37) - [apiWebdavProperties1/setFileProperties.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L38) +- [apiWebdavProperties1/setFileProperties.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L43) - [apiWebdavProperties1/setFileProperties.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L78) - [apiWebdavProperties1/setFileProperties.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L79) +- [apiWebdavProperties1/setFileProperties.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L84) #### [Cannot set custom webDav properties](https://github.com/owncloud/product/issues/264) - [apiWebdavProperties2/getFileProperties.feature:348](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L348) - [apiWebdavProperties2/getFileProperties.feature:353](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L353) +- [apiWebdavProperties2/getFileProperties.feature:358](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L358) - [apiWebdavProperties2/getFileProperties.feature:389](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L389) - [apiWebdavProperties2/getFileProperties.feature:394](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L394) +- [apiWebdavProperties2/getFileProperties.feature:399](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L399) ### Sync Synchronization features like etag propagation, setting mtime and locking files #### [Uploading an old method chunked file with checksum should fail using new DAV path](https://github.com/owncloud/ocis/issues/2323) - [apiMain/checksums.feature:369](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L369) +- [apiMain/checksums.feature:374](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L374) #### [Webdav LOCK operations](https://github.com/owncloud/ocis/issues/1284) - [apiWebdavLocks/exclusiveLocks.feature:18](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L18) @@ -298,16 +303,16 @@ Synchronization features like etag propagation, setting mtime and locking files File and sync features in a shared scenario ### [Different response containing exact and non exact match in response of getting sharees](https://github.com/owncloud/ocis/issues/2376) -- [apiSharees/sharees.feature:350](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L350) -- [apiSharees/sharees.feature:351](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L351) -- [apiSharees/sharees.feature:370](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L370) -- [apiSharees/sharees.feature:371](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L371) -- [apiSharees/sharees.feature:390](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L390) -- [apiSharees/sharees.feature:391](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L391) -- [apiSharees/sharees.feature:410](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L410) -- [apiSharees/sharees.feature:411](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L411) -- [apiSharees/sharees.feature:430](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L430) -- [apiSharees/sharees.feature:431](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L431) +- [apiSharees/sharees.feature:350](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L350) +- [apiSharees/sharees.feature:351](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L351) +- [apiSharees/sharees.feature:370](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L370) +- [apiSharees/sharees.feature:371](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L371) +- [apiSharees/sharees.feature:390](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L390) +- [apiSharees/sharees.feature:391](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L391) +- [apiSharees/sharees.feature:410](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L410) +- [apiSharees/sharees.feature:411](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L411) +- [apiSharees/sharees.feature:430](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L430) +- [apiSharees/sharees.feature:431](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L431) #### User cannot create a folder named Share - [apiShareManagementToShares/acceptShares.feature:373](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L373) @@ -315,25 +320,25 @@ File and sync features in a shared scenario #### [Response is empty when accepting a share](https://github.com/owncloud/product/issues/207) -- [apiShareManagementToShares/acceptShares.feature:311](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L311) +- [apiShareManagementToShares/acceptShares.feature:311](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L311) #### [cannot accept identical pending shares from different user serially](https://github.com/owncloud/ocis/issues/2131) -- [apiShareManagementToShares/acceptShares.feature:597](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L597) -- [apiShareManagementToShares/acceptShares.feature:658](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L658) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L174) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L175) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:214](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L214) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L215) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L47) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L48) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:236](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L236) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:237](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L237) +- [apiShareManagementToShares/acceptShares.feature:597](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L597) +- [apiShareManagementToShares/acceptShares.feature:658](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L658) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L174) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L175) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:214](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L214) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L215) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L47) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L48) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:236](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L236) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:237](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L237) #### [Shares received in different ways are not merged](https://github.com/owncloud/ocis/issues/2711) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:598](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L598) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:599](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L599) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:621](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L621) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:622](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L622) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:598](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L598) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:599](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L599) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:621](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L621) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:622](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L622) #### [sharing with group not available](https://github.com/owncloud/product/issues/293) The first two tests work against ocis. There must be something wrong in the CI setup. @@ -552,35 +557,50 @@ Scenario Outline: Retrieving folder quota of shared folder with quota when no qu - [apiWebdavProperties1/getQuota.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L58) - [apiWebdavProperties1/getQuota.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L59) Scenario Outline: Retrieving folder quota when quota is set and a file was uploaded +- [apiWebdavProperties1/getQuota.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L64) + Scenario Outline: Retrieving folder quota when quota is set and a file was uploaded - [apiWebdavProperties1/getQuota.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L77) - [apiWebdavProperties1/getQuota.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L78) Scenario Outline: Retrieving folder quota when quota is set and a file was received +- [apiWebdavProperties1/getQuota.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L83) + Scenario Outline: Retrieving folder quota when quota is set and a file was received - [apiWebdavProperties1/getQuota.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L98) - [apiWebdavProperties1/getQuota.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L99) Scenario Outline: Retrieving folder quota when no quota is set +- [apiWebdavProperties1/getQuota.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L104) + Scenario Outline: Retrieving folder quota when no quota is set - [apiWebdavProperties1/getQuota.feature:17](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L17) - [apiWebdavProperties1/getQuota.feature:18](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L18) Scenario Outline: Retrieving folder quota when quota is set +- [apiWebdavProperties1/getQuota.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L23) + Scenario Outline: Retrieving folder quota when quota is set - [apiWebdavProperties1/getQuota.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L32) - [apiWebdavProperties1/getQuota.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L33) +- [apiWebdavProperties1/getQuota.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L38) #### [Private link support](https://github.com/owncloud/product/issues/201) #### [oc:privatelink property not returned in webdav responses](https://github.com/owncloud/product/issues/262) - [apiWebdavProperties2/getFileProperties.feature:295](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L295) - [apiWebdavProperties2/getFileProperties.feature:296](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L296) +- [apiWebdavProperties2/getFileProperties.feature:301](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L301) #### [changing user quota gives ocs status 103 / Cannot set quota](https://github.com/owncloud/product/issues/247) _requires a [CS3 user provisioning api that can update the quota for a user](https://github.com/cs3org/cs3apis/pull/95#issuecomment-772780683)_ - [apiShareOperationsToShares2/uploadToShare.feature:193](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L193) - [apiShareOperationsToShares2/uploadToShare.feature:194](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L194) +- [apiShareOperationsToShares2/uploadToShare.feature:199](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L199) - [apiShareOperationsToShares2/uploadToShare.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L218) - [apiShareOperationsToShares2/uploadToShare.feature:219](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L219) +- [apiShareOperationsToShares2/uploadToShare.feature:224](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L224) - [apiShareOperationsToShares2/uploadToShare.feature:245](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L245) - [apiShareOperationsToShares2/uploadToShare.feature:246](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L246) +- [apiShareOperationsToShares2/uploadToShare.feature:251](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L251) - [apiShareOperationsToShares2/uploadToShare.feature:270](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L270) - [apiShareOperationsToShares2/uploadToShare.feature:271](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L271) +- [apiShareOperationsToShares2/uploadToShare.feature:276](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L276) - [apiShareOperationsToShares2/uploadToShare.feature:297](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L297) - [apiShareOperationsToShares2/uploadToShare.feature:298](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L298) +- [apiShareOperationsToShares2/uploadToShare.feature:303](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L303) #### [not possible to move file into a received folder](https://github.com/owncloud/ocis/issues/764) - [apiShareOperationsToShares1/changingFilesShare.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L24) @@ -622,8 +642,8 @@ Scenario Outline: Renaming a file to a path with extension .part should not be p #### [not possible to move file into a received folder](https://github.com/owncloud/ocis/issues/764) -- [apiVersions/fileVersionsSharingToShares.feature:219](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L219) -- [apiVersions/fileVersionsSharingToShares.feature:220](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L220) +- [apiVersions/fileVersionsSharingToShares.feature:219](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L219) +- [apiVersions/fileVersionsSharingToShares.feature:220](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L220) #### [Expiration date for shares is not implemented](https://github.com/owncloud/ocis/issues/1250) #### Expiration date of user shares @@ -807,7 +827,7 @@ _ocs: api compatibility, return correct status code_ - [apiShareUpdateToShares/updateShare.feature:288](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L288) #### [user can access version metadata of a received share before accepting it](https://github.com/owncloud/ocis/issues/760) -- [apiVersions/fileVersionsSharingToShares.feature:282](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L282) +- [apiVersions/fileVersionsSharingToShares.feature:282](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L282) #### [Share lists deleted user as 'user'](https://github.com/owncloud/ocis/issues/903) - [apiShareManagementBasicToShares/createShareToSharesFolder.feature:670](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L670) @@ -842,22 +862,30 @@ API, search, favorites, config, capabilities, not existing endpoints, CORS and o #### [Trying to access another user's file gives http 403 instead of 404](https://github.com/owncloud/ocis/issues/2175) _ocdav: api compatibility, return correct status code_ - [apiAuthWebDav/webDavMKCOLAuth.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMKCOLAuth.feature#L54) Scenario: send MKCOL requests to another user's webDav endpoints as normal user +- [apiAuthWebDav/webDavMKCOLAuth.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMKCOLAuth.feature#L68) Scenario: send MKCOL requests to another user's webDav endpoints as normal user using the spaces WebDAV API #### [trying to lock file of another user gives http 200](https://github.com/owncloud/ocis/issues/2176) - [apiAuthWebDav/webDavLOCKAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavLOCKAuth.feature#L58) Scenario: send LOCK requests to another user's webDav endpoints as normal user +- [apiAuthWebDav/webDavLOCKAuth.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavLOCKAuth.feature#L70) Scenario: send LOCK requests to another user's webDav endpoints as normal user using the spaces WebDAV API #### [Renaming a resource to banned name is allowed](https://github.com/owncloud/ocis/issues/1295) _ocdav: api compatibility, return correct status code_ - [apiAuthWebDav/webDavMOVEAuth.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMOVEAuth.feature#L57) Scenario: send MOVE requests to another user's webDav endpoints as normal user +- [apiAuthWebDav/webDavMOVEAuth.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMOVEAuth.feature#L66) Scenario: send MOVE requests to another user's webDav endpoints as normal user using the spaces WebDAV API #### [send POST requests to another user's webDav endpoints as normal user](https://github.com/owncloud/ocis/issues/1287) _ocdav: api compatibility, return correct status code_ - [apiAuthWebDav/webDavPOSTAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPOSTAuth.feature#L58) Scenario: send POST requests to another user's webDav endpoints as normal user +- [apiAuthWebDav/webDavPOSTAuth.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPOSTAuth.feature#L67) Scenario: send POST requests to another user's webDav endpoints as normal user using the spaces WebDAV API #### [Using double slash in URL to access a folder gives 501 and other status codes](https://github.com/owncloud/ocis/issues/1667) +- [apiAuthWebDav/webDavSpecialURLs.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L24) - [apiAuthWebDav/webDavSpecialURLs.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L34) +- [apiAuthWebDav/webDavSpecialURLs.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L45) - [apiAuthWebDav/webDavSpecialURLs.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L121) +- [apiAuthWebDav/webDavSpecialURLs.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L132) - [apiAuthWebDav/webDavSpecialURLs.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L163) +- [apiAuthWebDav/webDavSpecialURLs.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L174) #### [Default capabilities for normal user not same as in oC-core](https://github.com/owncloud/ocis/issues/1285) #### [Difference in response content of status.php and default capabilities](https://github.com/owncloud/ocis/issues/1286) @@ -899,14 +927,96 @@ Scenario Outline: search for entry with emoji by pattern - [apiWebdavOperations/search.feature:255](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L255) Scenario: search for entries across various folders by tags using REPORT method And other missing implementation of favorites -- [apiFavorites/favorites.feature:158](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L158) -- [apiFavorites/favorites.feature:159](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L159) -- [apiFavorites/favorites.feature:183](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L183) -- [apiFavorites/favorites.feature:184](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L184) -- [apiFavorites/favorites.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L216) -- [apiFavorites/favorites.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L217) -- [apiFavorites/favoritesSharingToShares.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L77) -- [apiFavorites/favoritesSharingToShares.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L78) +- [apiFavorites/favorites.feature:158](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L158) +- [apiFavorites/favorites.feature:159](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L159) +- [apiFavorites/favorites.feature:183](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L183) +- [apiFavorites/favorites.feature:184](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L184) +- [apiFavorites/favorites.feature:189](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L189) +- [apiFavorites/favorites.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L216) +- [apiFavorites/favorites.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L217) +- [apiFavorites/favorites.feature:222](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L222) +- [apiFavorites/favoritesSharingToShares.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L77) +- [apiFavorites/favoritesSharingToShares.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L78) + +#### [resource inside Shares dir is not found using the spaces WebDAV API](https://github.com/owncloud/ocis/issues/2968) +- [apiFavorites/favorites.feature:164](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L164) +- [apiFavorites/favoritesSharingToShares.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L27) +- [apiFavorites/favoritesSharingToShares.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L46) +- [apiFavorites/favoritesSharingToShares.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L64) +- [apiFavorites/favoritesSharingToShares.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L83) +- [apiFavorites/favoritesSharingToShares.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L102) +- [apiMain/checksums.feature:203](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L203) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L49) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L75) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L94) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L120) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:139](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L139) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:165](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L165) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:203](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L203) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:228](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L228) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:247](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L247) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:273](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L273) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:292](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L292) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:318](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L318) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:337](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L337) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:363](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L363) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:382](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L382) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:408](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L408) +- [apiShareOperationsToShares2/uploadToShare.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L47) +- [apiShareOperationsToShares2/uploadToShare.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L78) +- [apiShareOperationsToShares2/uploadToShare.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L111) +- [apiShareOperationsToShares2/uploadToShare.feature:140](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L140) +- [apiShareOperationsToShares2/uploadToShare.feature:171](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L171) +- [apiShareOperationsToShares2/uploadToShare.feature:346](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L346) +- [apiShareOperationsToShares2/uploadToShare.feature:347](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L347) +- [apiWebdavProperties1/copyFile.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L90) +- [apiWebdavProperties1/copyFile.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L117) +- [apiWebdavProperties1/copyFile.feature:293](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L293) +- [apiWebdavProperties1/copyFile.feature:316](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L316) +- [apiWebdavProperties1/copyFile.feature:344](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L344) +- [apiWebdavProperties1/copyFile.feature:374](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L374) +- [apiWebdavProperties1/copyFile.feature:403](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L403) +- [apiWebdavProperties1/copyFile.feature:432](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L432) +- [apiWebdavProperties1/copyFile.feature:516](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L516) +- [apiWebdavProperties1/copyFile.feature:549](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L549) +- [apiWebdavProperties1/copyFile.feature:581](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L581) +- [apiWebdavProperties1/copyFile.feature:613](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L613) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L37) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L38) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L39) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L60) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L61) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L62) +- [apiWebdavUploadTUS/uploadToShare.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L30) +- [apiWebdavUploadTUS/uploadToShare.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L49) +- [apiWebdavUploadTUS/uploadToShare.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L71) +- [apiWebdavUploadTUS/uploadToShare.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L91) +- [apiWebdavUploadTUS/uploadToShare.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L131) +- [apiWebdavUploadTUS/uploadToShare.feature:154](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L154) +- [apiWebdavUploadTUS/uploadToShare.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L176) +- [apiWebdavUploadTUS/uploadToShare.feature:198](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L198) +- [apiWebdavUploadTUS/uploadToShare.feature:222](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L222) +- [apiWebdavUploadTUS/uploadToShare.feature:246](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L246) +- [apiWebdavUploadTUS/uploadToShare.feature:292](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L292) +- [apiWebdavUploadTUS/uploadToShare.feature:316](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L316) +- [apiWebdavEtagPropagation1/moveFileFolder.feature:182](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L182) +- [apiWebdavEtagPropagation1/moveFileFolder.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L215) +- [apiWebdavEtagPropagation1/moveFileFolder.feature:255](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L255) +- [apiWebdavEtagPropagation1/moveFileFolder.feature:295](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L295) +- [apiWebdavEtagPropagation1/moveFileFolder.feature:335](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L335) +- [apiWebdavEtagPropagation1/moveFileFolder.feature:375](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L375) +- [apiWebdavEtagPropagation1/deleteFileFolder.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/deleteFileFolder.feature#L116) +- [apiWebdavEtagPropagation1/deleteFileFolder.feature:151](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/deleteFileFolder.feature#L151) +- [apiWebdavEtagPropagation1/deleteFileFolder.feature:188](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/deleteFileFolder.feature#L188) +- [apiWebdavEtagPropagation1/deleteFileFolder.feature:225](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/deleteFileFolder.feature#L225) +- [apiWebdavEtagPropagation2/copyFileFolder.feature:188](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/copyFileFolder.feature#L188) +- [apiWebdavEtagPropagation2/copyFileFolder.feature:231](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/copyFileFolder.feature#L231) +- [apiWebdavEtagPropagation2/createFolder.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/createFolder.feature#L82) +- [apiWebdavEtagPropagation2/createFolder.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/createFolder.feature#L112) +- [apiWebdavEtagPropagation2/upload.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L82) +- [apiWebdavEtagPropagation2/upload.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L111) +- [apiWebdavEtagPropagation2/upload.feature:141](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L141) +- [apiWebdavEtagPropagation2/upload.feature:171](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L171) #### [WWW-Authenticate header for unauthenticated requests is not clear](https://github.com/owncloud/ocis/issues/2285) Scenario Outline: Unauthenticated call @@ -990,90 +1100,92 @@ Scenario Outline: A disabled user cannot use webdav #### [App Passwords/Tokens for legacy WebDAV clients](https://github.com/owncloud/ocis/issues/197) - [apiAuthWebDav/webDavDELETEAuth.feature:136](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L136) +- [apiAuthWebDav/webDavDELETEAuth.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L150) - [apiAuthWebDav/webDavDELETEAuth.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L162) +- [apiAuthWebDav/webDavDELETEAuth.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L176) #### [various sharing settings cannot be set](https://github.com/owncloud/ocis/issues/1328) -- [apiCapabilities/capabilities.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L8) -- [apiCapabilities/capabilities.feature:15](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L15) -- [apiCapabilities/capabilities.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L21) -- [apiCapabilities/capabilities.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L27) -- [apiCapabilities/capabilities.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L33) -- [apiCapabilities/capabilities.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L38) -- [apiCapabilities/capabilities.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L80) -- [apiCapabilities/capabilities.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L93) -- [apiCapabilities/capabilities.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L107) -- [apiCapabilities/capabilities.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L116) -- [apiCapabilities/capabilities.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L126) -- [apiCapabilities/capabilities.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L133) -- [apiCapabilities/capabilities.feature:140](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L140) -- [apiCapabilities/capabilities.feature:146](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L146) -- [apiCapabilities/capabilities.feature:152](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L152) -- [apiCapabilities/capabilities.feature:159](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L159) -- [apiCapabilities/capabilities.feature:165](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L165) -- [apiCapabilities/capabilities.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L174) -- [apiCapabilities/capabilities.feature:184](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L184) -- [apiCapabilities/capabilities.feature:195](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L195) -- [apiCapabilities/capabilities.feature:206](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L206) -- [apiCapabilities/capabilities.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L216) -- [apiCapabilities/capabilities.feature:227](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L227) -- [apiCapabilities/capabilities.feature:239](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L239) -- [apiCapabilities/capabilities.feature:246](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L246) -- [apiCapabilities/capabilities.feature:253](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L253) -- [apiCapabilities/capabilities.feature:260](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L260) -- [apiCapabilities/capabilities.feature:283](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L283) -- [apiCapabilities/capabilities.feature:300](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L300) -- [apiCapabilities/capabilities.feature:321](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L321) -- [apiCapabilities/capabilities.feature:343](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L343) -- [apiCapabilities/capabilities.feature:365](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L365) -- [apiCapabilities/capabilities.feature:387](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L387) -- [apiCapabilities/capabilities.feature:412](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L412) -- [apiCapabilities/capabilities.feature:437](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L437) -- [apiCapabilities/capabilities.feature:462](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L462) -- [apiCapabilities/capabilities.feature:484](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L484) -- [apiCapabilities/capabilities.feature:506](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L506) -- [apiCapabilities/capabilities.feature:529](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L529) -- [apiCapabilities/capabilities.feature:554](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L554) -- [apiCapabilities/capabilities.feature:576](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L576) -- [apiCapabilities/capabilities.feature:598](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L598) -- [apiCapabilities/capabilities.feature:621](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L621) -- [apiCapabilities/capabilities.feature:645](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L645) -- [apiCapabilities/capabilities.feature:666](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L666) -- [apiCapabilities/capabilities.feature:688](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L688) -- [apiCapabilities/capabilities.feature:711](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L711) -- [apiCapabilities/capabilities.feature:737](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L737) -- [apiCapabilities/capabilities.feature:766](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L766) -- [apiCapabilities/capabilities.feature:795](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L795) -- [apiCapabilities/capabilities.feature:827](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L827) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L25) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L26) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L44) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L45) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L60) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L61) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L78) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L79) -- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L27) -- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L28) -- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L91) -- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L92) - -- [apiMain/caldav.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/caldav.feature#L8) -- [apiMain/caldav.feature:15](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/caldav.feature#L15) -- [apiMain/caldav.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/caldav.feature#L23) -- [apiMain/caldav.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/caldav.feature#L31) -- [apiMain/carddav.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/carddav.feature#L8) -- [apiMain/carddav.feature:15](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/carddav.feature#L15) -- [apiMain/carddav.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/carddav.feature#L23) -- [apiMain/carddav.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/carddav.feature#L31) - -- [apiTranslation/translation.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L21) -- [apiTranslation/translation.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L22) -- [apiTranslation/translation.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L23) -- [apiTranslation/translation.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L24) -- [apiTranslation/translation.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L25) -- [apiTranslation/translation.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L26) -- [apiTranslation/translation.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L27) -- [apiTranslation/translation.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L28) +- [apiCapabilities/capabilities.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L8) +- [apiCapabilities/capabilities.feature:15](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L15) +- [apiCapabilities/capabilities.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L21) +- [apiCapabilities/capabilities.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L27) +- [apiCapabilities/capabilities.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L33) +- [apiCapabilities/capabilities.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L38) +- [apiCapabilities/capabilities.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L80) +- [apiCapabilities/capabilities.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L93) +- [apiCapabilities/capabilities.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L107) +- [apiCapabilities/capabilities.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L116) +- [apiCapabilities/capabilities.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L126) +- [apiCapabilities/capabilities.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L133) +- [apiCapabilities/capabilities.feature:140](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L140) +- [apiCapabilities/capabilities.feature:146](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L146) +- [apiCapabilities/capabilities.feature:152](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L152) +- [apiCapabilities/capabilities.feature:159](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L159) +- [apiCapabilities/capabilities.feature:165](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L165) +- [apiCapabilities/capabilities.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L174) +- [apiCapabilities/capabilities.feature:184](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L184) +- [apiCapabilities/capabilities.feature:195](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L195) +- [apiCapabilities/capabilities.feature:206](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L206) +- [apiCapabilities/capabilities.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L216) +- [apiCapabilities/capabilities.feature:227](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L227) +- [apiCapabilities/capabilities.feature:239](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L239) +- [apiCapabilities/capabilities.feature:246](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L246) +- [apiCapabilities/capabilities.feature:253](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L253) +- [apiCapabilities/capabilities.feature:260](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L260) +- [apiCapabilities/capabilities.feature:283](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L283) +- [apiCapabilities/capabilities.feature:300](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L300) +- [apiCapabilities/capabilities.feature:321](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L321) +- [apiCapabilities/capabilities.feature:343](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L343) +- [apiCapabilities/capabilities.feature:365](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L365) +- [apiCapabilities/capabilities.feature:387](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L387) +- [apiCapabilities/capabilities.feature:412](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L412) +- [apiCapabilities/capabilities.feature:437](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L437) +- [apiCapabilities/capabilities.feature:462](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L462) +- [apiCapabilities/capabilities.feature:484](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L484) +- [apiCapabilities/capabilities.feature:506](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L506) +- [apiCapabilities/capabilities.feature:529](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L529) +- [apiCapabilities/capabilities.feature:554](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L554) +- [apiCapabilities/capabilities.feature:576](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L576) +- [apiCapabilities/capabilities.feature:598](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L598) +- [apiCapabilities/capabilities.feature:621](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L621) +- [apiCapabilities/capabilities.feature:645](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L645) +- [apiCapabilities/capabilities.feature:666](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L666) +- [apiCapabilities/capabilities.feature:688](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L688) +- [apiCapabilities/capabilities.feature:711](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L711) +- [apiCapabilities/capabilities.feature:737](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L737) +- [apiCapabilities/capabilities.feature:766](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L766) +- [apiCapabilities/capabilities.feature:795](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L795) +- [apiCapabilities/capabilities.feature:827](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L827) +- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L25) +- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L26) +- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L44) +- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L45) +- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L60) +- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L61) +- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L78) +- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L79) +- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L27) +- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L28) +- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L91) +- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L92) + +- [apiMain/caldav.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/caldav.feature#L8) +- [apiMain/caldav.feature:15](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/caldav.feature#L15) +- [apiMain/caldav.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/caldav.feature#L23) +- [apiMain/caldav.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/caldav.feature#L31) +- [apiMain/carddav.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/carddav.feature#L8) +- [apiMain/carddav.feature:15](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/carddav.feature#L15) +- [apiMain/carddav.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/carddav.feature#L23) +- [apiMain/carddav.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/carddav.feature#L31) + +- [apiTranslation/translation.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L21) +- [apiTranslation/translation.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L22) +- [apiTranslation/translation.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L23) +- [apiTranslation/translation.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L24) +- [apiTranslation/translation.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L25) +- [apiTranslation/translation.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L26) +- [apiTranslation/translation.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L27) +- [apiTranslation/translation.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L28) #### [Sharing a same file twice to the same group](https://github.com/owncloud/ocis/issues/1710) - [apiShareManagementBasicToShares/createShareToSharesFolder.feature:718](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L718) @@ -1101,8 +1213,10 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers #### [Blacklist files extensions](https://github.com/owncloud/ocis/issues/2177) - [apiWebdavProperties1/copyFile.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L133) - [apiWebdavProperties1/copyFile.feature:134](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L134) +- [apiWebdavProperties1/copyFile.feature:139](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L139) - [apiWebdavProperties1/createFolder.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L92) - [apiWebdavProperties1/createFolder.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L93) +- [apiWebdavProperties1/createFolder.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L98) - [apiWebdavUpload1/uploadFile.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L132) - [apiWebdavUpload1/uploadFile.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L133) - [apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature#L19) @@ -1171,30 +1285,45 @@ _The below features have been added after I last categorized them. AFAICT they a - [apiWebdavUploadTUS/checksums.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L82) - [apiWebdavUploadTUS/checksums.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L83) - [apiWebdavUploadTUS/checksums.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L84) +- [apiWebdavUploadTUS/checksums.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L89) +- [apiWebdavUploadTUS/checksums.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L90) - [apiWebdavUploadTUS/checksums.feature:168](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L168) - [apiWebdavUploadTUS/checksums.feature:169](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L169) +- [apiWebdavUploadTUS/checksums.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L174) - [apiWebdavUploadTUS/checksums.feature:219](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L219) - [apiWebdavUploadTUS/checksums.feature:220](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L220) - [apiWebdavUploadTUS/checksums.feature:221](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L221) - [apiWebdavUploadTUS/checksums.feature:222](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L222) +- [apiWebdavUploadTUS/checksums.feature:227](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L227) +- [apiWebdavUploadTUS/checksums.feature:228](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L228) - [apiWebdavUploadTUS/checksums.feature:274](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L274) - [apiWebdavUploadTUS/checksums.feature:275](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L275) - [apiWebdavUploadTUS/checksums.feature:276](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L276) - [apiWebdavUploadTUS/checksums.feature:277](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L277) +- [apiWebdavUploadTUS/checksums.feature:282](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L282) +- [apiWebdavUploadTUS/checksums.feature:283](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L283) - [apiWebdavUploadTUS/optionsRequest.feature:7](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L7) +- [apiWebdavUploadTUS/optionsRequest.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L20) - [apiWebdavUploadTUS/optionsRequest.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L31) -- [apiWebdavUploadTUS/optionsRequest.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L55) -- [apiWebdavUploadTUS/optionsRequest.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L79) +- [apiWebdavUploadTUS/optionsRequest.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L44) - [apiWebdavUploadTUS/uploadToShare.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L216) - [apiWebdavUploadTUS/uploadToShare.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L217) - [apiWebdavUploadTUS/uploadToShare.feature:240](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L240) - [apiWebdavUploadTUS/uploadToShare.feature:241](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L241) - [apiWebdavUploadTUS/uploadToShare.feature:264](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L264) - [apiWebdavUploadTUS/uploadToShare.feature:265](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L265) +- [apiWebdavUploadTUS/uploadToShare.feature:270](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L270) - [apiWebdavUploadTUS/uploadToShare.feature:310](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L310) - [apiWebdavUploadTUS/uploadToShare.feature:311](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L311) - [apiWebdavUploadTUS/uploadToShare.feature:361](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L361) - [apiWebdavUploadTUS/uploadToShare.feature:362](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L362) +- [apiWebdavUploadTUS/uploadToShare.feature:367](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L367) + +#### [TUS OPTIONS requests do not reply with TUS headers when invalid password](https://github.com/owncloud/ocis/issues/1012) +- [apiWebdavUploadTUS/optionsRequest.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L55) +- [apiWebdavUploadTUS/optionsRequest.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L68) +- [apiWebdavUploadTUS/optionsRequest.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L79) +- [apiWebdavUploadTUS/optionsRequest.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L93) #### [Share inaccessible if folder with same name was deleted and recreated](https://github.com/owncloud/ocis/issues/1787) - [apiShareReshareToShares1/reShare.feature:269](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares1/reShare.feature#L269) @@ -1236,26 +1365,30 @@ _ocs: api compatibility, return correct status code_ #### [[OC-storage] share-types field empty for shared file folder in webdav response](https://github.com/owncloud/ocis/issues/2144) - [apiWebdavProperties2/getFileProperties.feature:207](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L207) - [apiWebdavProperties2/getFileProperties.feature:208](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L208) +- [apiWebdavProperties2/getFileProperties.feature:213](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L213) #### [Creating a public link with all permissions(31) fails](https://github.com/owncloud/ocis/issues/2145) - [apiWebdavProperties2/getFileProperties.feature:265](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L265) - [apiWebdavProperties2/getFileProperties.feature:266](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L266) +- [apiWebdavProperties2/getFileProperties.feature:271](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L271) #### [Cannot move folder/file from one received share to another](https://github.com/owncloud/ocis/issues/2442) -- [apiShareUpdateToShares/updateShare.feature:241](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L241) +- [apiShareUpdateToShares/updateShare.feature:241](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L241) #### [Sharing folder and sub-folder with same user but different permission,the permission of sub-folder is not obeyed ](https://github.com/owncloud/ocis/issues/2440) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:264](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L264) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:296](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L296) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:328](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L328) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:423](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L423) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:455](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L455) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:264](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L264) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:296](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L296) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:328](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L328) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:423](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L423) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:455](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L455) #### [Trying to copy a file into a readonly share gives HTTP 500 error](https://github.com/owncloud/ocis/issues/2166) - [apiWebdavProperties1/copyFile.feature:453](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L453) - [apiWebdavProperties1/copyFile.feature:454](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L454) +- [apiWebdavProperties1/copyFile.feature:459](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L459) - [apiWebdavProperties1/copyFile.feature:479](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L479) - [apiWebdavProperties1/copyFile.feature:480](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L480) +- [apiWebdavProperties1/copyFile.feature:485](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L485) #### [downloading an old version of a file returns 501](https://github.com/owncloud/ocis/issues/2261) - [apiVersions/fileVersions.feature:437](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersions.feature#L437) @@ -1263,19 +1396,19 @@ _ocs: api compatibility, return correct status code_ - [apiVersions/fileVersionsSharingToShares.feature:305](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L305) #### [file versions do not report the version author](https://github.com/owncloud/ocis/issues/2914) -- [apiVersions/fileVersionAuthor.feature:14](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L14) -- [apiVersions/fileVersionAuthor.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L36) -- [apiVersions/fileVersionAuthor.feature:56](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L56) -- [apiVersions/fileVersionAuthor.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L75) -- [apiVersions/fileVersionAuthor.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L101) -- [apiVersions/fileVersionAuthor.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L128) -- [apiVersions/fileVersionAuthor.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L155) -- [apiVersions/fileVersionAuthor.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L180) -- [apiVersions/fileVersionAuthor.feature:220](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L220) +- [apiVersions/fileVersionAuthor.feature:14](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L14) +- [apiVersions/fileVersionAuthor.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L36) +- [apiVersions/fileVersionAuthor.feature:56](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L56) +- [apiVersions/fileVersionAuthor.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L75) +- [apiVersions/fileVersionAuthor.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L101) +- [apiVersions/fileVersionAuthor.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L128) +- [apiVersions/fileVersionAuthor.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L155) +- [apiVersions/fileVersionAuthor.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L180) +- [apiVersions/fileVersionAuthor.feature:220](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L220) ### [Allow public link sharing only for certain groups feature not implemented] -- [apiSharePublicLink2/allowGroupToCreatePublicLinks.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/allowGroupToCreatePublicLinks.feature#L35) -- [apiSharePublicLink2/allowGroupToCreatePublicLinks.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/allowGroupToCreatePublicLinks.feature#L91) +- [apiSharePublicLink2/allowGroupToCreatePublicLinks.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/allowGroupToCreatePublicLinks.feature#L35) +- [apiSharePublicLink2/allowGroupToCreatePublicLinks.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/allowGroupToCreatePublicLinks.feature#L91) #### [Shares to deleted group listed in the response](https://github.com/owncloud/ocis/issues/2441) - [apiShareManagementBasicToShares/createShareToSharesFolder.feature:504](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L504) @@ -1297,13 +1430,30 @@ _ocs: api compatibility, return correct status code_ - [apiWebdavOperations/downloadFile.feature:169](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/downloadFile.feature#L169) - [apiWebdavOperations/downloadFile.feature:170](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/downloadFile.feature#L170) +#### [Creating a new folder which is a substring of Shares leads to Unknown Error](https://github.com/owncloud/ocis/issues/3033) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L79) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L96) + ### [send PUT requests to another user's webDav endpoints as normal user](https://github.com/owncloud/ocis/issues/2893) -- [apiAuthWebDav/webDavPUTAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPUTAuth.feature#L58) +- [apiAuthWebDav/webDavPUTAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPUTAuth.feature#L58) +- [apiAuthWebDav/webDavPUTAuth.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPUTAuth.feature#L70) #### [moveShareInsideAnotherShare behaves differently on oCIS than oC10](https://github.com/owncloud/ocis/issues/3047) -- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L25) -- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L86) -- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L100) +- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L25) +- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L86) +- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L100) + +#### [TUS upload file with invalid name sends false response](https://github.com/owncloud/ocis/issues/3050) +- [apiWebdavUploadTUS/uploadFile.feature:206](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFile.feature#L206) +- [apiWebdavUploadTUS/uploadFile.feature:207](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFile.feature#L207) +- [apiWebdavUploadTUS/uploadFile.feature:209](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFile.feature#L209) + +#### [unable to create resource using TUS inside Shares dir](https://github.com/owncloud/ocis/issues/3048) +- [apiWebdavUploadTUS/uploadFileMtimeShares.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFileMtimeShares.feature#L32) +- [apiWebdavUploadTUS/uploadFileMtimeShares.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFileMtimeShares.feature#L50) +- [apiWebdavUploadTUS/uploadFileMtimeShares.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFileMtimeShares.feature#L70) +- [apiWebdavUploadTUS/uploadFileMtimeShares.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFileMtimeShares.feature#L90) +- [apiWebdavUploadTUS/uploadToShare.feature:341](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L341) Note: always have an empty line at the end of this file. The bash script that processes this file may not process a scenario reference on the last line. diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index 3bd38fb77b8..c3bb9e212c3 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -12,20 +12,20 @@ Basic file management like up and download, move, copy, properties, quota, trash - [apiTrashbin/trashbinFilesFolders.feature:231](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L231) #### [downloading an old version of a file returns 501](https://github.com/owncloud/ocis/issues/2261) -- [apiVersions/fileVersions.feature:437](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersions.feature#L437) -- [apiVersions/fileVersions.feature:455](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersions.feature#L455) -- [apiVersions/fileVersionsSharingToShares.feature:305](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L305) +- [apiVersions/fileVersions.feature:437](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersions.feature#L437) +- [apiVersions/fileVersions.feature:455](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersions.feature#L455) +- [apiVersions/fileVersionsSharingToShares.feature:305](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L305) #### [file versions do not report the version author](https://github.com/owncloud/ocis/issues/2914) -- [apiVersions/fileVersionAuthor.feature:14](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L14) -- [apiVersions/fileVersionAuthor.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L36) -- [apiVersions/fileVersionAuthor.feature:56](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L56) -- [apiVersions/fileVersionAuthor.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L75) -- [apiVersions/fileVersionAuthor.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L101) -- [apiVersions/fileVersionAuthor.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L128) -- [apiVersions/fileVersionAuthor.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L155) -- [apiVersions/fileVersionAuthor.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L180) -- [apiVersions/fileVersionAuthor.feature:220](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L220) +- [apiVersions/fileVersionAuthor.feature:14](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L14) +- [apiVersions/fileVersionAuthor.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L36) +- [apiVersions/fileVersionAuthor.feature:56](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L56) +- [apiVersions/fileVersionAuthor.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L75) +- [apiVersions/fileVersionAuthor.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L101) +- [apiVersions/fileVersionAuthor.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L128) +- [apiVersions/fileVersionAuthor.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L155) +- [apiVersions/fileVersionAuthor.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L180) +- [apiVersions/fileVersionAuthor.feature:220](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L220) #### [Getting information about a folder overwritten by a file gives 500 error instead of 404](https://github.com/owncloud/ocis/issues/1239) - [apiWebdavProperties1/copyFile.feature:287](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L287) @@ -37,19 +37,25 @@ Basic file management like up and download, move, copy, properties, quota, trash _ocdav: double check the webdav property parsing when custom namespaces are used_ - [apiWebdavProperties1/setFileProperties.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L37) - [apiWebdavProperties1/setFileProperties.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L38) -- [apiWebdavProperties1/setFileProperties.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L78) +- [apiWebdavProperties1/setFileProperties.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L43) + [apiWebdavProperties1/setFileProperties.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L78) - [apiWebdavProperties1/setFileProperties.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L79) +- [apiWebdavProperties1/setFileProperties.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/setFileProperties.feature#L84) #### [Cannot set custom webDav properties](https://github.com/owncloud/product/issues/264) - [apiWebdavProperties2/getFileProperties.feature:348](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L348) - [apiWebdavProperties2/getFileProperties.feature:353](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L353) +- [apiWebdavProperties2/getFileProperties.feature:358](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L358) - [apiWebdavProperties2/getFileProperties.feature:389](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L389) - [apiWebdavProperties2/getFileProperties.feature:394](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L394) +- [apiWebdavProperties2/getFileProperties.feature:399](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L399) + ### Sync Synchronization features like etag propagation, setting mtime and locking files #### [Uploading an old method chunked file with checksum should fail using new DAV path](https://github.com/owncloud/ocis/issues/2323) - [apiMain/checksums.feature:369](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L369) +- [apiMain/checksums.feature:374](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L374) #### [Webdav LOCK operations](https://github.com/owncloud/ocis/issues/1284) - [apiWebdavLocks/exclusiveLocks.feature:18](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L18) @@ -311,41 +317,41 @@ Synchronization features like etag propagation, setting mtime and locking files File and sync features in a shared scenario ### [Different response containing exact and non exact match in response of getting sharees](https://github.com/owncloud/ocis/issues/2376) -- [apiSharees/sharees.feature:350](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L350) -- [apiSharees/sharees.feature:351](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L351) -- [apiSharees/sharees.feature:370](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L370) -- [apiSharees/sharees.feature:371](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L371) -- [apiSharees/sharees.feature:390](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L390) -- [apiSharees/sharees.feature:391](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L391) -- [apiSharees/sharees.feature:410](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L410) -- [apiSharees/sharees.feature:411](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L411) -- [apiSharees/sharees.feature:430](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L430) -- [apiSharees/sharees.feature:431](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L431) +- [apiSharees/sharees.feature:350](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L350) +- [apiSharees/sharees.feature:351](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L351) +- [apiSharees/sharees.feature:370](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L370) +- [apiSharees/sharees.feature:371](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L371) +- [apiSharees/sharees.feature:390](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L390) +- [apiSharees/sharees.feature:391](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L391) +- [apiSharees/sharees.feature:410](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L410) +- [apiSharees/sharees.feature:411](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L411) +- [apiSharees/sharees.feature:430](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L430) +- [apiSharees/sharees.feature:431](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharees/sharees.feature#L431) #### User cannot create a folder named Share - [apiShareManagementToShares/acceptShares.feature:373](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L373) - [apiShareManagementToShares/acceptShares.feature:407](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L407) #### [Response is empty when accepting a share](https://github.com/owncloud/product/issues/207) -- [apiShareManagementToShares/acceptShares.feature:311](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L311) +- [apiShareManagementToShares/acceptShares.feature:311](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L311) #### [cannot accept identical pending shares from different user serially](https://github.com/owncloud/ocis/issues/2131) -- [apiShareManagementToShares/acceptShares.feature:597](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L597) -- [apiShareManagementToShares/acceptShares.feature:658](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L658) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L174) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L175) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:214](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L214) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L215) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L47) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L48) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:236](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L236) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:237](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L237) +- [apiShareManagementToShares/acceptShares.feature:597](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L597) +- [apiShareManagementToShares/acceptShares.feature:658](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/acceptShares.feature#L658) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L174) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L175) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:214](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L214) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L215) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L47) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L48) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:236](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L236) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:237](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L237) #### [Shares received in different ways are not merged](https://github.com/owncloud/ocis/issues/2711) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:598](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L598) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:599](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L599) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:621](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L621) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:622](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L622) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:598](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L598) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:599](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L599) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:621](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L621) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:622](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L622) #### [Fix accepting/denying group shares](https://github.com/cs3org/reva/issues/1769) @@ -470,7 +476,7 @@ File and sync features in a shared scenario #### [Upload-only shares must not overwrite but create a separate file](https://github.com/owncloud/ocis/issues/1267) - [apiSharePublicLink2/uploadToPublicLinkShare.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/uploadToPublicLinkShare.feature#L24) -- [apiSharePublicLink2/uploadToPublicLinkShare.feature:268](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/uploadToPublicLinkShare.feature#L268) +- [apiSharePublicLink2/uploadToPublicLinkShare.feature:268](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/uploadToPublicLinkShare.feature#L268) #### [Set quota over settings](https://github.com/owncloud/ocis/issues/1290) _requires a [CS3 user provisioning api that can update the quota for a user](https://github.com/cs3org/cs3apis/pull/95#issuecomment-772780683)_ @@ -540,36 +546,50 @@ Scenario Outline: Retrieving folder quota of shared folder with quota when no qu - [apiWebdavProperties1/getQuota.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L58) - [apiWebdavProperties1/getQuota.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L59) Scenario Outline: Retrieving folder quota when quota is set and a file was uploaded +- [apiWebdavProperties1/getQuota.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L64) + Scenario Outline: Retrieving folder quota when quota is set and a file was uploaded - [apiWebdavProperties1/getQuota.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L77) - [apiWebdavProperties1/getQuota.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L78) Scenario Outline: Retrieving folder quota when quota is set and a file was received +- [apiWebdavProperties1/getQuota.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L83) + Scenario Outline: Retrieving folder quota when quota is set and a file was received - [apiWebdavProperties1/getQuota.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L98) - [apiWebdavProperties1/getQuota.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L99) Scenario Outline: Retrieving folder quota when no quota is set +- [apiWebdavProperties1/getQuota.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L104) + Scenario Outline: Retrieving folder quota when no quota is set - [apiWebdavProperties1/getQuota.feature:17](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L17) - [apiWebdavProperties1/getQuota.feature:18](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L18) Scenario Outline: Retrieving folder quota when quota is set +- [apiWebdavProperties1/getQuota.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L23) + Scenario Outline: Retrieving folder quota when quota is set - [apiWebdavProperties1/getQuota.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L32) - [apiWebdavProperties1/getQuota.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L33) +- [apiWebdavProperties1/getQuota.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/getQuota.feature#L38) #### [Private link support](https://github.com/owncloud/product/issues/201) #### [oc:privatelink property not returned in webdav responses](https://github.com/owncloud/product/issues/262) - [apiWebdavProperties2/getFileProperties.feature:295](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L295) - [apiWebdavProperties2/getFileProperties.feature:296](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L296) +- [apiWebdavProperties2/getFileProperties.feature:301](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L301) #### [changing user quota gives ocs status 103 / Cannot set quota](https://github.com/owncloud/product/issues/247) _requires a [CS3 user provisioning api that can update the quota for a user](https://github.com/cs3org/cs3apis/pull/95#issuecomment-772780683)_ - [apiShareOperationsToShares2/uploadToShare.feature:193](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L193) - [apiShareOperationsToShares2/uploadToShare.feature:194](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L194) +- [apiShareOperationsToShares2/uploadToShare.feature:199](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L199) - [apiShareOperationsToShares2/uploadToShare.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L218) - [apiShareOperationsToShares2/uploadToShare.feature:219](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L219) +- [apiShareOperationsToShares2/uploadToShare.feature:224](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L224) - [apiShareOperationsToShares2/uploadToShare.feature:245](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L245) - [apiShareOperationsToShares2/uploadToShare.feature:246](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L246) +- [apiShareOperationsToShares2/uploadToShare.feature:251](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L251) - [apiShareOperationsToShares2/uploadToShare.feature:270](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L270) - [apiShareOperationsToShares2/uploadToShare.feature:271](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L271) +- [apiShareOperationsToShares2/uploadToShare.feature:276](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L276) - [apiShareOperationsToShares2/uploadToShare.feature:297](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L297) - [apiShareOperationsToShares2/uploadToShare.feature:298](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L298) - +- [apiShareOperationsToShares2/uploadToShare.feature:303](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L303) #### [not possible to move file into a received folder](https://github.com/owncloud/ocis/issues/764) @@ -639,8 +659,8 @@ Scenario Outline: Renaming a file to a path with extension .part should not be p - [apiShareReshareToShares3/reShareWithExpiryDate.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares3/reShareWithExpiryDate.feature#L37) ####[not possible to move file into a received folder](https://github.com/owncloud/ocis/issues/764) -- [apiVersions/fileVersionsSharingToShares.feature:219](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L219) -- [apiVersions/fileVersionsSharingToShares.feature:220](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L220) +- [apiVersions/fileVersionsSharingToShares.feature:219](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L219) +- [apiVersions/fileVersionsSharingToShares.feature:220](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L220) #### [Expiration date for shares is not implemented](https://github.com/owncloud/ocis/issues/1250) #### Expiration date of user shares @@ -824,7 +844,7 @@ _ocs: api compatibility, return correct status code_ - [apiShareUpdateToShares/updateShare.feature:288](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L288) #### [user can access version metadata of a received share before accepting it](https://github.com/owncloud/ocis/issues/760) -- [apiVersions/fileVersionsSharingToShares.feature:282](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L282) +- [apiVersions/fileVersionsSharingToShares.feature:282](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L282) #### [Share lists deleted user as 'user'](https://github.com/owncloud/ocis/issues/903) - [apiShareManagementBasicToShares/createShareToSharesFolder.feature:670](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L670) @@ -859,22 +879,30 @@ API, search, favorites, config, capabilities, not existing endpoints, CORS and o #### [Trying to access another user's file gives http 403 instead of 404](https://github.com/owncloud/ocis/issues/2175) _ocdav: api compatibility, return correct status code_ - [apiAuthWebDav/webDavMKCOLAuth.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMKCOLAuth.feature#L54) Scenario: send MKCOL requests to another user's webDav endpoints as normal user +- [apiAuthWebDav/webDavMKCOLAuth.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMKCOLAuth.feature#L68) Scenario: send MKCOL requests to another user's webDav endpoints as normal user using the spaces WebDAV API #### [trying to lock file of another user gives http 200](https://github.com/owncloud/ocis/issues/2176) - [apiAuthWebDav/webDavLOCKAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavLOCKAuth.feature#L58) Scenario: send LOCK requests to another user's webDav endpoints as normal user +- [apiAuthWebDav/webDavLOCKAuth.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavLOCKAuth.feature#L70) Scenario: send LOCK requests to another user's webDav endpoints as normal user using the spaces WebDAV API #### [Renaming a resource to banned name is allowed](https://github.com/owncloud/ocis/issues/1295) _ocdav: api compatibility, return correct status code_ - [apiAuthWebDav/webDavMOVEAuth.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMOVEAuth.feature#L57) Scenario: send MOVE requests to another user's webDav endpoints as normal user +- [apiAuthWebDav/webDavMOVEAuth.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavMOVEAuth.feature#L66) Scenario: send MOVE requests to another user's webDav endpoints as normal user using the spaces WebDAV API #### [send POST requests to another user's webDav endpoints as normal user](https://github.com/owncloud/ocis/issues/1287) _ocdav: api compatibility, return correct status code_ - [apiAuthWebDav/webDavPOSTAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPOSTAuth.feature#L58) Scenario: send POST requests to another user's webDav endpoints as normal user +- [apiAuthWebDav/webDavPOSTAuth.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPOSTAuth.feature#L67) Scenario: send POST requests to another user's webDav endpoints as normal user using the spaces WebDAV API #### [Using double slash in URL to access a folder gives 501 and other status codes](https://github.com/owncloud/ocis/issues/1667) +- [apiAuthWebDav/webDavSpecialURLs.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L24) - [apiAuthWebDav/webDavSpecialURLs.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L34) +- [apiAuthWebDav/webDavSpecialURLs.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L45) - [apiAuthWebDav/webDavSpecialURLs.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L121) +- [apiAuthWebDav/webDavSpecialURLs.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L132) - [apiAuthWebDav/webDavSpecialURLs.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L163) +- [apiAuthWebDav/webDavSpecialURLs.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavSpecialURLs.feature#L174) #### [Default capabilities for normal user not same as in oC-core](https://github.com/owncloud/ocis/issues/1285) #### [Difference in response content of status.php and default capabilities](https://github.com/owncloud/ocis/issues/1286) @@ -916,14 +944,96 @@ Scenario Outline: search for entry with emoji by pattern - [apiWebdavOperations/search.feature:255](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L255) Scenario: search for entries across various folders by tags using REPORT method And other missing implementation of favorites -- [apiFavorites/favorites.feature:158](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L158) -- [apiFavorites/favorites.feature:159](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L159) -- [apiFavorites/favorites.feature:183](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L183) -- [apiFavorites/favorites.feature:184](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L184) -- [apiFavorites/favorites.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L216) -- [apiFavorites/favorites.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L217) -- [apiFavorites/favoritesSharingToShares.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L77) -- [apiFavorites/favoritesSharingToShares.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L78) +- [apiFavorites/favorites.feature:158](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L158) +- [apiFavorites/favorites.feature:159](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L159) +- [apiFavorites/favorites.feature:183](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L183) +- [apiFavorites/favorites.feature:184](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L184) +- [apiFavorites/favorites.feature:189](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L189) +- [apiFavorites/favorites.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L216) +- [apiFavorites/favorites.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L217) +- [apiFavorites/favorites.feature:222](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L222) +- [apiFavorites/favoritesSharingToShares.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L77) +- [apiFavorites/favoritesSharingToShares.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L78) + +#### [resource inside Shares dir is not found using the spaces WebDAV API](https://github.com/owncloud/ocis/issues/2968) +- [apiFavorites/favorites.feature:164](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L164) +- [apiFavorites/favoritesSharingToShares.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L27) +- [apiFavorites/favoritesSharingToShares.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L46) +- [apiFavorites/favoritesSharingToShares.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L64) +- [apiFavorites/favoritesSharingToShares.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L83) +- [apiFavorites/favoritesSharingToShares.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L102) +- [apiMain/checksums.feature:203](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L203) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L49) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L75) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L94) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L120) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:139](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L139) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:165](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L165) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:203](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L203) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:228](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L228) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:247](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L247) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:273](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L273) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:292](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L292) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:318](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L318) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:337](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L337) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:363](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L363) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:382](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L382) +- [apiShareOperationsToShares2/getWebDAVSharePermissions.feature:408](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/getWebDAVSharePermissions.feature#L408) +- [apiShareOperationsToShares2/uploadToShare.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L47) +- [apiShareOperationsToShares2/uploadToShare.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L78) +- [apiShareOperationsToShares2/uploadToShare.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L111) +- [apiShareOperationsToShares2/uploadToShare.feature:140](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L140) +- [apiShareOperationsToShares2/uploadToShare.feature:171](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L171) +- [apiShareOperationsToShares2/uploadToShare.feature:346](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L346) +- [apiShareOperationsToShares2/uploadToShare.feature:347](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/uploadToShare.feature#L347) +- [apiWebdavProperties1/copyFile.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L90) +- [apiWebdavProperties1/copyFile.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L117) +- [apiWebdavProperties1/copyFile.feature:293](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L293) +- [apiWebdavProperties1/copyFile.feature:316](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L316) +- [apiWebdavProperties1/copyFile.feature:344](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L344) +- [apiWebdavProperties1/copyFile.feature:374](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L374) +- [apiWebdavProperties1/copyFile.feature:403](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L403) +- [apiWebdavProperties1/copyFile.feature:432](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L432) +- [apiWebdavProperties1/copyFile.feature:516](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L516) +- [apiWebdavProperties1/copyFile.feature:549](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L549) +- [apiWebdavProperties1/copyFile.feature:581](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L581) +- [apiWebdavProperties1/copyFile.feature:613](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L613) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L37) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L38) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L39) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L60) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L61) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L62) +- [apiWebdavUploadTUS/uploadToShare.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L30) +- [apiWebdavUploadTUS/uploadToShare.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L49) +- [apiWebdavUploadTUS/uploadToShare.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L71) +- [apiWebdavUploadTUS/uploadToShare.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L91) +- [apiWebdavUploadTUS/uploadToShare.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L131) +- [apiWebdavUploadTUS/uploadToShare.feature:154](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L154) +- [apiWebdavUploadTUS/uploadToShare.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L176) +- [apiWebdavUploadTUS/uploadToShare.feature:198](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L198) +- [apiWebdavUploadTUS/uploadToShare.feature:222](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L222) +- [apiWebdavUploadTUS/uploadToShare.feature:246](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L246) +- [apiWebdavUploadTUS/uploadToShare.feature:292](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L292) +- [apiWebdavUploadTUS/uploadToShare.feature:316](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L316) +- [apiWebdavEtagPropagation1/moveFileFolder.feature:182](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L182) +- [apiWebdavEtagPropagation1/moveFileFolder.feature:215](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L215) +- [apiWebdavEtagPropagation1/moveFileFolder.feature:255](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L255) +- [apiWebdavEtagPropagation1/moveFileFolder.feature:295](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L295) +- [apiWebdavEtagPropagation1/moveFileFolder.feature:335](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L335) +- [apiWebdavEtagPropagation1/moveFileFolder.feature:375](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/moveFileFolder.feature#L375) +- [apiWebdavEtagPropagation1/deleteFileFolder.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/deleteFileFolder.feature#L116) +- [apiWebdavEtagPropagation1/deleteFileFolder.feature:151](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/deleteFileFolder.feature#L151) +- [apiWebdavEtagPropagation1/deleteFileFolder.feature:188](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/deleteFileFolder.feature#L188) +- [apiWebdavEtagPropagation1/deleteFileFolder.feature:225](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation1/deleteFileFolder.feature#L225) +- [apiWebdavEtagPropagation2/copyFileFolder.feature:188](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/copyFileFolder.feature#L188) +- [apiWebdavEtagPropagation2/copyFileFolder.feature:231](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/copyFileFolder.feature#L231) +- [apiWebdavEtagPropagation2/createFolder.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/createFolder.feature#L82) +- [apiWebdavEtagPropagation2/createFolder.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/createFolder.feature#L112) +- [apiWebdavEtagPropagation2/upload.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L82) +- [apiWebdavEtagPropagation2/upload.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L111) +- [apiWebdavEtagPropagation2/upload.feature:141](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L141) +- [apiWebdavEtagPropagation2/upload.feature:171](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L171) #### [WWW-Authenticate header for unauthenticated requests is not clear](https://github.com/owncloud/ocis/issues/2285) Scenario Outline: Unauthenticated call @@ -1007,90 +1117,92 @@ Scenario Outline: A disabled user cannot use webdav #### [App Passwords/Tokens for legacy WebDAV clients](https://github.com/owncloud/ocis/issues/197) - [apiAuthWebDav/webDavDELETEAuth.feature:136](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L136) +- [apiAuthWebDav/webDavDELETEAuth.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L150) - [apiAuthWebDav/webDavDELETEAuth.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L162) +- [apiAuthWebDav/webDavDELETEAuth.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavDELETEAuth.feature#L176) #### [various sharing settings cannot be set](https://github.com/owncloud/ocis/issues/1328) -- [apiCapabilities/capabilities.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L8) -- [apiCapabilities/capabilities.feature:15](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L15) -- [apiCapabilities/capabilities.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L21) -- [apiCapabilities/capabilities.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L27) -- [apiCapabilities/capabilities.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L33) -- [apiCapabilities/capabilities.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L38) -- [apiCapabilities/capabilities.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L80) -- [apiCapabilities/capabilities.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L93) -- [apiCapabilities/capabilities.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L107) -- [apiCapabilities/capabilities.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L116) -- [apiCapabilities/capabilities.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L126) -- [apiCapabilities/capabilities.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L133) -- [apiCapabilities/capabilities.feature:140](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L140) -- [apiCapabilities/capabilities.feature:146](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L146) -- [apiCapabilities/capabilities.feature:152](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L152) -- [apiCapabilities/capabilities.feature:159](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L159) -- [apiCapabilities/capabilities.feature:165](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L165) -- [apiCapabilities/capabilities.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L174) -- [apiCapabilities/capabilities.feature:184](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L184) -- [apiCapabilities/capabilities.feature:195](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L195) -- [apiCapabilities/capabilities.feature:206](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L206) -- [apiCapabilities/capabilities.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L216) -- [apiCapabilities/capabilities.feature:227](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L227) -- [apiCapabilities/capabilities.feature:239](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L239) -- [apiCapabilities/capabilities.feature:246](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L246) -- [apiCapabilities/capabilities.feature:253](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L253) -- [apiCapabilities/capabilities.feature:260](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L260) -- [apiCapabilities/capabilities.feature:283](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L283) -- [apiCapabilities/capabilities.feature:300](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L300) -- [apiCapabilities/capabilities.feature:321](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L321) -- [apiCapabilities/capabilities.feature:343](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L343) -- [apiCapabilities/capabilities.feature:365](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L365) -- [apiCapabilities/capabilities.feature:387](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L387) -- [apiCapabilities/capabilities.feature:412](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L412) -- [apiCapabilities/capabilities.feature:437](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L437) -- [apiCapabilities/capabilities.feature:462](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L462) -- [apiCapabilities/capabilities.feature:484](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L484) -- [apiCapabilities/capabilities.feature:506](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L506) -- [apiCapabilities/capabilities.feature:529](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L529) -- [apiCapabilities/capabilities.feature:554](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L554) -- [apiCapabilities/capabilities.feature:576](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L576) -- [apiCapabilities/capabilities.feature:598](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L598) -- [apiCapabilities/capabilities.feature:621](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L621) -- [apiCapabilities/capabilities.feature:645](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L645) -- [apiCapabilities/capabilities.feature:666](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L666) -- [apiCapabilities/capabilities.feature:688](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L688) -- [apiCapabilities/capabilities.feature:711](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L711) -- [apiCapabilities/capabilities.feature:737](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L737) -- [apiCapabilities/capabilities.feature:766](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L766) -- [apiCapabilities/capabilities.feature:795](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L795) -- [apiCapabilities/capabilities.feature:827](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L827) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L25) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L26) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L44) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L45) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L60) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L61) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L78) -- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L79) -- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L27) -- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L28) -- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L91) -- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L92) - -- [apiMain/caldav.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/caldav.feature#L8) -- [apiMain/caldav.feature:15](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/caldav.feature#L15) -- [apiMain/caldav.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/caldav.feature#L23) -- [apiMain/caldav.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/caldav.feature#L31) -- [apiMain/carddav.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/carddav.feature#L8) -- [apiMain/carddav.feature:15](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/carddav.feature#L15) -- [apiMain/carddav.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/carddav.feature#L23) -- [apiMain/carddav.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/carddav.feature#L31) - -- [apiTranslation/translation.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L21) -- [apiTranslation/translation.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L22) -- [apiTranslation/translation.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L23) -- [apiTranslation/translation.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L24) -- [apiTranslation/translation.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L25) -- [apiTranslation/translation.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L26) -- [apiTranslation/translation.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L27) -- [apiTranslation/translation.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L28) +- [apiCapabilities/capabilities.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L8) +- [apiCapabilities/capabilities.feature:15](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L15) +- [apiCapabilities/capabilities.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L21) +- [apiCapabilities/capabilities.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L27) +- [apiCapabilities/capabilities.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L33) +- [apiCapabilities/capabilities.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L38) +- [apiCapabilities/capabilities.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L80) +- [apiCapabilities/capabilities.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L93) +- [apiCapabilities/capabilities.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L107) +- [apiCapabilities/capabilities.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L116) +- [apiCapabilities/capabilities.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L126) +- [apiCapabilities/capabilities.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L133) +- [apiCapabilities/capabilities.feature:140](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L140) +- [apiCapabilities/capabilities.feature:146](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L146) +- [apiCapabilities/capabilities.feature:152](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L152) +- [apiCapabilities/capabilities.feature:159](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L159) +- [apiCapabilities/capabilities.feature:165](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L165) +- [apiCapabilities/capabilities.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L174) +- [apiCapabilities/capabilities.feature:184](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L184) +- [apiCapabilities/capabilities.feature:195](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L195) +- [apiCapabilities/capabilities.feature:206](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L206) +- [apiCapabilities/capabilities.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L216) +- [apiCapabilities/capabilities.feature:227](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L227) +- [apiCapabilities/capabilities.feature:239](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L239) +- [apiCapabilities/capabilities.feature:246](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L246) +- [apiCapabilities/capabilities.feature:253](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L253) +- [apiCapabilities/capabilities.feature:260](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L260) +- [apiCapabilities/capabilities.feature:283](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L283) +- [apiCapabilities/capabilities.feature:300](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L300) +- [apiCapabilities/capabilities.feature:321](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L321) +- [apiCapabilities/capabilities.feature:343](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L343) +- [apiCapabilities/capabilities.feature:365](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L365) +- [apiCapabilities/capabilities.feature:387](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L387) +- [apiCapabilities/capabilities.feature:412](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L412) +- [apiCapabilities/capabilities.feature:437](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L437) +- [apiCapabilities/capabilities.feature:462](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L462) +- [apiCapabilities/capabilities.feature:484](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L484) +- [apiCapabilities/capabilities.feature:506](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L506) +- [apiCapabilities/capabilities.feature:529](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L529) +- [apiCapabilities/capabilities.feature:554](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L554) +- [apiCapabilities/capabilities.feature:576](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L576) +- [apiCapabilities/capabilities.feature:598](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L598) +- [apiCapabilities/capabilities.feature:621](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L621) +- [apiCapabilities/capabilities.feature:645](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L645) +- [apiCapabilities/capabilities.feature:666](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L666) +- [apiCapabilities/capabilities.feature:688](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L688) +- [apiCapabilities/capabilities.feature:711](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L711) +- [apiCapabilities/capabilities.feature:737](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L737) +- [apiCapabilities/capabilities.feature:766](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L766) +- [apiCapabilities/capabilities.feature:795](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L795) +- [apiCapabilities/capabilities.feature:827](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L827) +- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L25) +- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L26) +- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L44) +- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L45) +- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L60) +- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L61) +- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L78) +- [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L79) +- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L27) +- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L28) +- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L91) +- [apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWhenShareWithOnlyMembershipGroups.feature#L92) + +- [apiMain/caldav.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/caldav.feature#L8) +- [apiMain/caldav.feature:15](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/caldav.feature#L15) +- [apiMain/caldav.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/caldav.feature#L23) +- [apiMain/caldav.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/caldav.feature#L31) +- [apiMain/carddav.feature:8](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/carddav.feature#L8) +- [apiMain/carddav.feature:15](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/carddav.feature#L15) +- [apiMain/carddav.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/carddav.feature#L23) +- [apiMain/carddav.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/carddav.feature#L31) + +- [apiTranslation/translation.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L21) +- [apiTranslation/translation.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L22) +- [apiTranslation/translation.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L23) +- [apiTranslation/translation.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L24) +- [apiTranslation/translation.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L25) +- [apiTranslation/translation.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L26) +- [apiTranslation/translation.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L27) +- [apiTranslation/translation.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L28) #### [Sharing a same file twice to the same group](https://github.com/owncloud/ocis/issues/1710) - [apiShareManagementBasicToShares/createShareToSharesFolder.feature:718](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L718) @@ -1118,8 +1230,10 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers #### [Blacklist files extensions](https://github.com/owncloud/ocis/issues/2177) - [apiWebdavProperties1/copyFile.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L133) - [apiWebdavProperties1/copyFile.feature:134](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L134) +- [apiWebdavProperties1/copyFile.feature:139](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L139) - [apiWebdavProperties1/createFolder.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L92) - [apiWebdavProperties1/createFolder.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L93) +- [apiWebdavProperties1/createFolder.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L98) - [apiWebdavUpload1/uploadFile.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L132) - [apiWebdavUpload1/uploadFile.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L133) - [apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature#L19) @@ -1188,30 +1302,45 @@ _The below features have been added after I last categorized them. AFAICT they a - [apiWebdavUploadTUS/checksums.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L82) - [apiWebdavUploadTUS/checksums.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L83) - [apiWebdavUploadTUS/checksums.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L84) +- [apiWebdavUploadTUS/checksums.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L89) +- [apiWebdavUploadTUS/checksums.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L90) - [apiWebdavUploadTUS/checksums.feature:168](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L168) - [apiWebdavUploadTUS/checksums.feature:169](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L169) +- [apiWebdavUploadTUS/checksums.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L174) - [apiWebdavUploadTUS/checksums.feature:219](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L219) - [apiWebdavUploadTUS/checksums.feature:220](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L220) - [apiWebdavUploadTUS/checksums.feature:221](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L221) - [apiWebdavUploadTUS/checksums.feature:222](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L222) +- [apiWebdavUploadTUS/checksums.feature:227](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L227) +- [apiWebdavUploadTUS/checksums.feature:228](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L228) - [apiWebdavUploadTUS/checksums.feature:274](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L274) - [apiWebdavUploadTUS/checksums.feature:275](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L275) - [apiWebdavUploadTUS/checksums.feature:276](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L276) - [apiWebdavUploadTUS/checksums.feature:277](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L277) +- [apiWebdavUploadTUS/checksums.feature:282](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L282) +- [apiWebdavUploadTUS/checksums.feature:283](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/checksums.feature#L283) - [apiWebdavUploadTUS/optionsRequest.feature:7](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L7) +- [apiWebdavUploadTUS/optionsRequest.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L20) - [apiWebdavUploadTUS/optionsRequest.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L31) -- [apiWebdavUploadTUS/optionsRequest.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L55) -- [apiWebdavUploadTUS/optionsRequest.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L79) +- [apiWebdavUploadTUS/optionsRequest.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L44) - [apiWebdavUploadTUS/uploadToShare.feature:216](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L216) - [apiWebdavUploadTUS/uploadToShare.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L217) - [apiWebdavUploadTUS/uploadToShare.feature:240](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L240) - [apiWebdavUploadTUS/uploadToShare.feature:241](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L241) - [apiWebdavUploadTUS/uploadToShare.feature:264](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L264) - [apiWebdavUploadTUS/uploadToShare.feature:265](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L265) +- [apiWebdavUploadTUS/uploadToShare.feature:270](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L270) - [apiWebdavUploadTUS/uploadToShare.feature:310](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L310) - [apiWebdavUploadTUS/uploadToShare.feature:311](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L311) - [apiWebdavUploadTUS/uploadToShare.feature:361](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L361) - [apiWebdavUploadTUS/uploadToShare.feature:362](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L362) +- [apiWebdavUploadTUS/uploadToShare.feature:367](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L367) + +#### [TUS OPTIONS requests do not reply with TUS headers when invalid password](https://github.com/owncloud/ocis/issues/1012) +- [apiWebdavUploadTUS/optionsRequest.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L55) +- [apiWebdavUploadTUS/optionsRequest.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L68) +- [apiWebdavUploadTUS/optionsRequest.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L79) +- [apiWebdavUploadTUS/optionsRequest.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/optionsRequest.feature#L93) #### [Share inaccessible if folder with same name was deleted and recreated](https://github.com/owncloud/ocis/issues/1787) - [apiShareReshareToShares1/reShare.feature:269](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareReshareToShares1/reShare.feature#L269) @@ -1253,30 +1382,34 @@ _ocs: api compatibility, return correct status code_ #### [[OC-storage] share-types field empty for shared file folder in webdav response](https://github.com/owncloud/ocis/issues/2144) - [apiWebdavProperties2/getFileProperties.feature:207](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L207) - [apiWebdavProperties2/getFileProperties.feature:208](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L208) +- [apiWebdavProperties2/getFileProperties.feature:213](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L213) #### [Creating a public link with all permissions(31) fails](https://github.com/owncloud/ocis/issues/2145) - [apiWebdavProperties2/getFileProperties.feature:265](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L265) - [apiWebdavProperties2/getFileProperties.feature:266](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L266) +- [apiWebdavProperties2/getFileProperties.feature:271](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties2/getFileProperties.feature#L271) #### [Cannot move folder/file from one received share to another](https://github.com/owncloud/ocis/issues/2442) -- [apiShareUpdateToShares/updateShare.feature:241](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L241) +- [apiShareUpdateToShares/updateShare.feature:241](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L241) #### [Sharing folder and sub-folder with same user but different permission,the permission of sub-folder is not obeyed ](https://github.com/owncloud/ocis/issues/2440) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:264](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L264) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:296](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L296) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:328](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L328) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:423](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L423) -- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:455](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L455) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:264](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L264) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:296](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L296) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:328](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L328) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:423](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L423) +- [apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature:455](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareReceivedInMultipleWays.feature#L455) #### [Trying to copy a file into a readonly share gives HTTP 500 error](https://github.com/owncloud/ocis/issues/2166) - [apiWebdavProperties1/copyFile.feature:453](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L453) - [apiWebdavProperties1/copyFile.feature:454](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L454) +- [apiWebdavProperties1/copyFile.feature:459](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L459) - [apiWebdavProperties1/copyFile.feature:479](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L479) - [apiWebdavProperties1/copyFile.feature:480](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L480) +- [apiWebdavProperties1/copyFile.feature:485](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L485) ### [Allow public link sharing only for certain groups feature not implemented] -- [apiSharePublicLink2/allowGroupToCreatePublicLinks.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/allowGroupToCreatePublicLinks.feature#L35) -- [apiSharePublicLink2/allowGroupToCreatePublicLinks.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/allowGroupToCreatePublicLinks.feature#L91) +- [apiSharePublicLink2/allowGroupToCreatePublicLinks.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/allowGroupToCreatePublicLinks.feature#L35) +- [apiSharePublicLink2/allowGroupToCreatePublicLinks.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiSharePublicLink2/allowGroupToCreatePublicLinks.feature#L91) #### [Shares to deleted group listed in the response](https://github.com/owncloud/ocis/issues/2441) - [apiShareManagementBasicToShares/createShareToSharesFolder.feature:504](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L504) @@ -1298,13 +1431,30 @@ _ocs: api compatibility, return correct status code_ - [apiWebdavOperations/downloadFile.feature:169](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/downloadFile.feature#L169) - [apiWebdavOperations/downloadFile.feature:170](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/downloadFile.feature#L170) +#### [Creating a new folder which is a substring of Shares leads to Unknown Error](https://github.com/owncloud/ocis/issues/3033) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L79) +- [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L96) + ### [send PUT requests to another user's webDav endpoints as normal user](https://github.com/owncloud/ocis/issues/2893) - [apiAuthWebDav/webDavPUTAuth.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPUTAuth.feature#L58) +- [apiAuthWebDav/webDavPUTAuth.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuthWebDav/webDavPUTAuth.feature#L70) #### [moveShareInsideAnotherShare behaves differently on oCIS than oC10](https://github.com/owncloud/ocis/issues/3047) -- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L25) -- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L86) -- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L100) +- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L25) +- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L86) +- [apiShareManagementToShares/moveShareInsideAnotherShare.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/moveShareInsideAnotherShare.feature#L100) + +#### [TUS upload file with invalid name sends false response](https://github.com/owncloud/ocis/issues/3050) +- [apiWebdavUploadTUS/uploadFile.feature:206](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFile.feature#L206) +- [apiWebdavUploadTUS/uploadFile.feature:207](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFile.feature#L207) +- [apiWebdavUploadTUS/uploadFile.feature:209](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFile.feature#L209) + +#### [unable to create resource using TUS inside Shares dir](https://github.com/owncloud/ocis/issues/3048) +- [apiWebdavUploadTUS/uploadFileMtimeShares.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFileMtimeShares.feature#L32) +- [apiWebdavUploadTUS/uploadFileMtimeShares.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFileMtimeShares.feature#L50) +- [apiWebdavUploadTUS/uploadFileMtimeShares.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFileMtimeShares.feature#L70) +- [apiWebdavUploadTUS/uploadFileMtimeShares.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFileMtimeShares.feature#L90) +- [apiWebdavUploadTUS/uploadToShare.feature:341](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L341) Note: always have an empty line at the end of this file. The bash script that processes this file may not process a scenario reference on the last line. From 7660ee26dce790f84cbbdb3c1f227cd8a1242616 Mon Sep 17 00:00:00 2001 From: David Christofas Date: Tue, 8 Feb 2022 10:16:30 +0100 Subject: [PATCH 38/49] Cleanup code (#2516) * pre-compile the chunking regex * reduce type conversions * add changelog --- changelog/unreleased/cleanup-code.md | 6 ++++ .../services/owncloud/ocdav/errors/error.go | 8 +++-- .../owncloud/ocdav/propfind/propfind.go | 29 ++++++++++--------- .../http/services/owncloud/ocdav/proppatch.go | 17 ++++++----- .../services/owncloud/ocdav/publicfile.go | 2 +- internal/http/services/owncloud/ocdav/put.go | 7 +---- .../http/services/owncloud/ocdav/report.go | 2 +- .../http/services/owncloud/ocdav/trashbin.go | 21 ++++++++------ .../http/services/owncloud/ocdav/versions.go | 2 +- .../owncloud/ocs/response/response.go | 2 +- pkg/storage/fs/owncloudsql/upload.go | 6 +--- pkg/storage/utils/chunking/chunking.go | 8 +++-- pkg/storage/utils/decomposedfs/upload.go | 11 ++----- pkg/storage/utils/eosfs/upload.go | 6 +--- pkg/storage/utils/localfs/upload.go | 6 +--- 15 files changed, 66 insertions(+), 67 deletions(-) create mode 100644 changelog/unreleased/cleanup-code.md diff --git a/changelog/unreleased/cleanup-code.md b/changelog/unreleased/cleanup-code.md new file mode 100644 index 00000000000..3bdbdb72602 --- /dev/null +++ b/changelog/unreleased/cleanup-code.md @@ -0,0 +1,6 @@ +Enhancement: Cleaned up some code + +- Reduced type conversions []byte <-> string +- pre-compile chunking regex + +https://github.com/cs3org/reva/pull/2516 diff --git a/internal/http/services/owncloud/ocdav/errors/error.go b/internal/http/services/owncloud/ocdav/errors/error.go index bb395d21975..b7fde0d3453 100644 --- a/internal/http/services/owncloud/ocdav/errors/error.go +++ b/internal/http/services/owncloud/ocdav/errors/error.go @@ -19,6 +19,7 @@ package errors import ( + "bytes" "encoding/xml" "net/http" @@ -76,9 +77,12 @@ func Marshal(code code, message string, header string) ([]byte, error) { Header: header, }) if err != nil { - return []byte(""), err + return nil, err } - return []byte(xml.Header + string(xmlstring)), err + var buf bytes.Buffer + buf.WriteString(xml.Header) + buf.Write(xmlstring) + return buf.Bytes(), err } // ErrorXML holds the xml representation of an error diff --git a/internal/http/services/owncloud/ocdav/propfind/propfind.go b/internal/http/services/owncloud/ocdav/propfind/propfind.go index 2af6777e9f7..874ba81e80d 100644 --- a/internal/http/services/owncloud/ocdav/propfind/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind/propfind.go @@ -19,6 +19,7 @@ package propfind import ( + "bytes" "context" "encoding/json" "encoding/xml" @@ -218,7 +219,7 @@ func (p *Handler) propfindResponse(ctx context.Context, w http.ResponseWriter, r } w.WriteHeader(http.StatusMultiStatus) - if _, err := w.Write([]byte(propRes)); err != nil { + if _, err := w.Write(propRes); err != nil { log.Err(err).Msg("error writing response") } } @@ -545,24 +546,26 @@ func ReadPropfind(r io.Reader) (pf XML, status int, err error) { } // MultistatusResponse converts a list of resource infos into a multistatus response string -func MultistatusResponse(ctx context.Context, pf *XML, mds []*provider.ResourceInfo, publicURL, ns string, linkshares map[string]struct{}) (string, error) { +func MultistatusResponse(ctx context.Context, pf *XML, mds []*provider.ResourceInfo, publicURL, ns string, linkshares map[string]struct{}) ([]byte, error) { responses := make([]*ResponseXML, 0, len(mds)) for i := range mds { res, err := mdToPropResponse(ctx, pf, mds[i], publicURL, ns, linkshares) if err != nil { - return "", err + return nil, err } responses = append(responses, res) } responsesXML, err := xml.Marshal(&responses) if err != nil { - return "", err + return nil, err } - msg := `` - msg += string(responsesXML) + `` - return msg, nil + var buf bytes.Buffer + buf.WriteString(``) + buf.Write(responsesXML) + buf.WriteString(``) + return buf.Bytes(), nil } // mdToPropResponse converts the CS3 metadata into a webdav PropResponse @@ -590,7 +593,7 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p // -2 indicates unknown (default) // -3 indicates unlimited quota := net.PropQuotaUnknown - size := fmt.Sprintf("%d", md.Size) + size := strconv.FormatUint(md.Size, 10) // TODO refactor helper functions: GetOpaqueJSONEncoded(opaque, key string, *struct) err, GetOpaquePlainEncoded(opaque, key) value, err // or use ok like pattern and return bool? if md.Opaque != nil && md.Opaque.Map != nil { @@ -693,7 +696,7 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p } else { checksums.WriteString(" MD5:") } - checksums.WriteString(string(e.Value)) + checksums.Write(e.Value) } if e, ok := md.Opaque.Map["adler32"]; ok { if checksums.Len() == 0 { @@ -701,7 +704,7 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p } else { checksums.WriteString(" ADLER32:") } - checksums.WriteString(string(e.Value)) + checksums.Write(e.Value) } } if checksums.Len() > 0 { @@ -861,7 +864,7 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p } else { checksums.WriteString(" MD5:") } - checksums.WriteString(string(e.Value)) + checksums.Write(e.Value) } if e, ok := md.Opaque.Map["adler32"]; ok { if checksums.Len() == 0 { @@ -869,7 +872,7 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p } else { checksums.WriteString(" ADLER32:") } - checksums.WriteString(string(e.Value)) + checksums.Write(e.Value) } } if checksums.Len() > 13 { diff --git a/internal/http/services/owncloud/ocdav/proppatch.go b/internal/http/services/owncloud/ocdav/proppatch.go index 0846579e28c..9724a26a47b 100644 --- a/internal/http/services/owncloud/ocdav/proppatch.go +++ b/internal/http/services/owncloud/ocdav/proppatch.go @@ -19,6 +19,7 @@ package ocdav import ( + "bytes" "context" "encoding/xml" "fmt" @@ -309,12 +310,12 @@ func (s *svc) handleProppatchResponse(ctx context.Context, w http.ResponseWriter w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol") w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8") w.WriteHeader(http.StatusMultiStatus) - if _, err := w.Write([]byte(propRes)); err != nil { + if _, err := w.Write(propRes); err != nil { log.Err(err).Msg("error writing response") } } -func (s *svc) formatProppatchResponse(ctx context.Context, acceptedProps []xml.Name, removedProps []xml.Name, ref string) (string, error) { +func (s *svc) formatProppatchResponse(ctx context.Context, acceptedProps []xml.Name, removedProps []xml.Name, ref string) ([]byte, error) { responses := make([]propfind.ResponseXML, 0, 1) response := propfind.ResponseXML{ Href: net.EncodePath(ref), @@ -346,13 +347,15 @@ func (s *svc) formatProppatchResponse(ctx context.Context, acceptedProps []xml.N responses = append(responses, response) responsesXML, err := xml.Marshal(&responses) if err != nil { - return "", err + return nil, err } - msg := `` - msg += string(responsesXML) + `` - return msg, nil + var buf bytes.Buffer + buf.WriteString(``) + buf.Write(responsesXML) + buf.WriteString(``) + return buf.Bytes(), nil } func (s *svc) isBooleanProperty(prop string) bool { diff --git a/internal/http/services/owncloud/ocdav/publicfile.go b/internal/http/services/owncloud/ocdav/publicfile.go index 512a9e39ba8..1cf12b7e2fc 100644 --- a/internal/http/services/owncloud/ocdav/publicfile.go +++ b/internal/http/services/owncloud/ocdav/publicfile.go @@ -122,7 +122,7 @@ func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns s w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol") w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8") w.WriteHeader(http.StatusMultiStatus) - if _, err := w.Write([]byte(propRes)); err != nil { + if _, err := w.Write(propRes); err != nil { sublog.Err(err).Msg("error writing response") } } diff --git a/internal/http/services/owncloud/ocdav/put.go b/internal/http/services/owncloud/ocdav/put.go index 9dc53e38a89..aa3d2c2f2b0 100644 --- a/internal/http/services/owncloud/ocdav/put.go +++ b/internal/http/services/owncloud/ocdav/put.go @@ -298,12 +298,7 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ return } - ok, err := chunking.IsChunked(ref.Path) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - if ok { + if chunking.IsChunked(ref.Path) { chunk, err := chunking.GetChunkBLOBInfo(ref.Path) if err != nil { w.WriteHeader(http.StatusInternalServerError) diff --git a/internal/http/services/owncloud/ocdav/report.go b/internal/http/services/owncloud/ocdav/report.go index 8670074d111..89785f92944 100644 --- a/internal/http/services/owncloud/ocdav/report.go +++ b/internal/http/services/owncloud/ocdav/report.go @@ -124,7 +124,7 @@ func (s *svc) doFilterFiles(w http.ResponseWriter, r *http.Request, ff *reportFi w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol") w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8") w.WriteHeader(http.StatusMultiStatus) - if _, err := w.Write([]byte(responsesXML)); err != nil { + if _, err := w.Write(responsesXML); err != nil { log.Err(err).Msg("error writing response") } } diff --git a/internal/http/services/owncloud/ocdav/trashbin.go b/internal/http/services/owncloud/ocdav/trashbin.go index 49470f73811..6bf159160e3 100644 --- a/internal/http/services/owncloud/ocdav/trashbin.go +++ b/internal/http/services/owncloud/ocdav/trashbin.go @@ -19,6 +19,7 @@ package ocdav import ( + "bytes" "context" "encoding/xml" "fmt" @@ -197,7 +198,7 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol") w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8") w.WriteHeader(http.StatusMultiStatus) - _, err = w.Write([]byte(propRes)) + _, err = w.Write(propRes) if err != nil { sublog.Error().Err(err).Msg("error writing body") return @@ -302,14 +303,14 @@ func (h *TrashbinHandler) listTrashbin(w http.ResponseWriter, r *http.Request, s w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol") w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8") w.WriteHeader(http.StatusMultiStatus) - _, err = w.Write([]byte(propRes)) + _, err = w.Write(propRes) if err != nil { sublog.Error().Err(err).Msg("error writing body") return } } -func (h *TrashbinHandler) formatTrashPropfind(ctx context.Context, s *svc, u *userpb.User, pf *propfind.XML, items []*provider.RecycleItem) (string, error) { +func (h *TrashbinHandler) formatTrashPropfind(ctx context.Context, s *svc, u *userpb.User, pf *propfind.XML, items []*provider.RecycleItem) ([]byte, error) { responses := make([]*propfind.ResponseXML, 0, len(items)+1) // add trashbin dir . entry responses = append(responses, &propfind.ResponseXML{ @@ -336,19 +337,21 @@ func (h *TrashbinHandler) formatTrashPropfind(ctx context.Context, s *svc, u *us for i := range items { res, err := h.itemToPropResponse(ctx, s, u, pf, items[i]) if err != nil { - return "", err + return nil, err } responses = append(responses, res) } responsesXML, err := xml.Marshal(&responses) if err != nil { - return "", err + return nil, err } - msg := `` - msg += string(responsesXML) + `` - return msg, nil + var buf bytes.Buffer + buf.WriteString(``) + buf.Write(responsesXML) + buf.WriteString(``) + return buf.Bytes(), nil } // itemToPropResponse needs to create a listing that contains a key and destination diff --git a/internal/http/services/owncloud/ocdav/versions.go b/internal/http/services/owncloud/ocdav/versions.go index ebe609a925d..9a2d756d310 100644 --- a/internal/http/services/owncloud/ocdav/versions.go +++ b/internal/http/services/owncloud/ocdav/versions.go @@ -174,7 +174,7 @@ func (h *VersionsHandler) doListVersions(w http.ResponseWriter, r *http.Request, w.Header().Set(net.HeaderDav, "1, 3, extended-mkcol") w.Header().Set(net.HeaderContentType, "application/xml; charset=utf-8") w.WriteHeader(http.StatusMultiStatus) - _, err = w.Write([]byte(propRes)) + _, err = w.Write(propRes) if err != nil { sublog.Error().Err(err).Msg("error writing body") return diff --git a/internal/http/services/owncloud/ocs/response/response.go b/internal/http/services/owncloud/ocs/response/response.go index f92af90875f..a13fb851bb6 100644 --- a/internal/http/services/owncloud/ocs/response/response.go +++ b/internal/http/services/owncloud/ocs/response/response.go @@ -189,7 +189,7 @@ func encodeXML(res Response) ([]byte, error) { return nil, err } b := new(bytes.Buffer) - b.Write([]byte(xml.Header)) + b.WriteString(xml.Header) b.Write(marshalled) return b.Bytes(), nil } diff --git a/pkg/storage/fs/owncloudsql/upload.go b/pkg/storage/fs/owncloudsql/upload.go index da167efe91a..cb03dcca002 100644 --- a/pkg/storage/fs/owncloudsql/upload.go +++ b/pkg/storage/fs/owncloudsql/upload.go @@ -55,11 +55,7 @@ func (fs *owncloudsqlfs) Upload(ctx context.Context, ref *provider.Reference, r uploadInfo := upload.(*fileUpload) p := uploadInfo.info.Storage["InternalDestination"] - ok, err := chunking.IsChunked(p) - if err != nil { - return errors.Wrap(err, "owncloudsql: error checking path") - } - if ok { + if chunking.IsChunked(p) { var assembledFile string p, assembledFile, err = fs.chunkHandler.WriteChunk(p, r) if err != nil { diff --git a/pkg/storage/utils/chunking/chunking.go b/pkg/storage/utils/chunking/chunking.go index 656ec14fe91..f7df79fdf6e 100644 --- a/pkg/storage/utils/chunking/chunking.go +++ b/pkg/storage/utils/chunking/chunking.go @@ -29,10 +29,14 @@ import ( "strings" ) +var ( + chunkingPathRE = regexp.MustCompile(`-chunking-\w+-[0-9]+-[0-9]+$`) +) + // IsChunked checks if a given path refers to a chunk or not -func IsChunked(fn string) (bool, error) { +func IsChunked(fn string) bool { // FIXME: also need to check whether the OC-Chunked header is set - return regexp.MatchString(`-chunking-\w+-[0-9]+-[0-9]+$`, fn) + return chunkingPathRE.MatchString(fn) } // ChunkBLOBInfo stores info about a particular chunk diff --git a/pkg/storage/utils/decomposedfs/upload.go b/pkg/storage/utils/decomposedfs/upload.go index 8218fae25af..79f236516f2 100644 --- a/pkg/storage/utils/decomposedfs/upload.go +++ b/pkg/storage/utils/decomposedfs/upload.go @@ -64,11 +64,7 @@ func (fs *Decomposedfs) Upload(ctx context.Context, ref *provider.Reference, r i uploadInfo := upload.(*fileUpload) p := uploadInfo.info.Storage["NodeName"] - ok, err := chunking.IsChunked(p) // check chunking v1 - if err != nil { - return errors.Wrap(err, "Decomposedfs: error checking path") - } - if ok { + if chunking.IsChunked(p) { // check chunking v1 var assembledFile string p, assembledFile, err = fs.chunkHandler.WriteChunk(p, r) if err != nil { @@ -360,10 +356,7 @@ func (fs *Decomposedfs) GetUpload(ctx context.Context, id string) (tusd.Upload, // This method can also handle lookups for paths which contain chunking information. func (fs *Decomposedfs) lookupNode(ctx context.Context, spaceRoot *node.Node, path string) (*node.Node, error) { p := path - isChunked, err := chunking.IsChunked(path) - if err != nil { - return nil, err - } + isChunked := chunking.IsChunked(path) if isChunked { chunkInfo, err := chunking.GetChunkBLOBInfo(path) if err != nil { diff --git a/pkg/storage/utils/eosfs/upload.go b/pkg/storage/utils/eosfs/upload.go index 9d20e01194f..a1c6d61a141 100644 --- a/pkg/storage/utils/eosfs/upload.go +++ b/pkg/storage/utils/eosfs/upload.go @@ -39,11 +39,7 @@ func (fs *eosfs) Upload(ctx context.Context, ref *provider.Reference, r io.ReadC return errtypes.PermissionDenied("eos: cannot upload under the virtual share folder") } - ok, err := chunking.IsChunked(p) - if err != nil { - return errors.Wrap(err, "eos: error checking path") - } - if ok { + if chunking.IsChunked(p) { var assembledFile string p, assembledFile, err = fs.chunkHandler.WriteChunk(p, r) if err != nil { diff --git a/pkg/storage/utils/localfs/upload.go b/pkg/storage/utils/localfs/upload.go index 36963e7bfb4..fe5036e0dbb 100644 --- a/pkg/storage/utils/localfs/upload.go +++ b/pkg/storage/utils/localfs/upload.go @@ -49,11 +49,7 @@ func (fs *localfs) Upload(ctx context.Context, ref *provider.Reference, r io.Rea uploadInfo := upload.(*fileUpload) p := uploadInfo.info.Storage["InternalDestination"] - ok, err := chunking.IsChunked(p) - if err != nil { - return errors.Wrap(err, "localfs: error checking path") - } - if ok { + if chunking.IsChunked(p) { var assembledFile string p, assembledFile, err = fs.chunkHandler.WriteChunk(p, r) if err != nil { From d58c9273f953613b84db3f4ae2198724790d2a36 Mon Sep 17 00:00:00 2001 From: PKiran <39373750+kiranparajuli589@users.noreply.github.com> Date: Tue, 8 Feb 2022 17:58:00 +0545 Subject: [PATCH 39/49] bump core commit id and update the expected failures (#2518) --- .drone.env | 2 +- .../expected-failures-on-OCIS-storage.md | 272 +++++++++++------- .../expected-failures-on-S3NG-storage.md | 272 +++++++++++------- 3 files changed, 335 insertions(+), 211 deletions(-) diff --git a/.drone.env b/.drone.env index 6d8169ead2b..5bca50c0099 100644 --- a/.drone.env +++ b/.drone.env @@ -1,3 +1,3 @@ # The test runner source for API tests -CORE_COMMITID=f4773164f276ce9307bedb1d24fa06c0023b7ad3 +CORE_COMMITID=a0fb3af23939f5573c7b0a54d68204b6c705ed81 CORE_BRANCH=master diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index 911c952fe91..6766ac8f2c4 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -48,74 +48,108 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks/exclusiveLocks.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L19) - [apiWebdavLocks/exclusiveLocks.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L20) - [apiWebdavLocks/exclusiveLocks.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L21) -- [apiWebdavLocks/exclusiveLocks.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L36) -- [apiWebdavLocks/exclusiveLocks.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L37) -- [apiWebdavLocks/exclusiveLocks.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L38) -- [apiWebdavLocks/exclusiveLocks.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L39) -- [apiWebdavLocks/exclusiveLocks.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L55) -- [apiWebdavLocks/exclusiveLocks.feature:56](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L56) -- [apiWebdavLocks/exclusiveLocks.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L57) -- [apiWebdavLocks/exclusiveLocks.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L58) -- [apiWebdavLocks/exclusiveLocks.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L74) -- [apiWebdavLocks/exclusiveLocks.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L75) -- [apiWebdavLocks/exclusiveLocks.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L76) +- [apiWebdavLocks/exclusiveLocks.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L26) +- [apiWebdavLocks/exclusiveLocks.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L27) +- [apiWebdavLocks/exclusiveLocks.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L43) +- [apiWebdavLocks/exclusiveLocks.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L44) +- [apiWebdavLocks/exclusiveLocks.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L45) +- [apiWebdavLocks/exclusiveLocks.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L46) +- [apiWebdavLocks/exclusiveLocks.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L51) +- [apiWebdavLocks/exclusiveLocks.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L52) +- [apiWebdavLocks/exclusiveLocks.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L69) +- [apiWebdavLocks/exclusiveLocks.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L70) +- [apiWebdavLocks/exclusiveLocks.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L71) +- [apiWebdavLocks/exclusiveLocks.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L72) - [apiWebdavLocks/exclusiveLocks.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L77) -- [apiWebdavLocks/exclusiveLocks.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L93) +- [apiWebdavLocks/exclusiveLocks.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L78) - [apiWebdavLocks/exclusiveLocks.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L94) - [apiWebdavLocks/exclusiveLocks.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L95) - [apiWebdavLocks/exclusiveLocks.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L96) -- [apiWebdavLocks/exclusiveLocks.feature:114](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L114) -- [apiWebdavLocks/exclusiveLocks.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L115) -- [apiWebdavLocks/exclusiveLocks.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L116) -- [apiWebdavLocks/exclusiveLocks.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L117) -- [apiWebdavLocks/exclusiveLocks.feature:135](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L135) -- [apiWebdavLocks/exclusiveLocks.feature:136](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L136) -- [apiWebdavLocks/exclusiveLocks.feature:137](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L137) -- [apiWebdavLocks/exclusiveLocks.feature:138](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L138) +- [apiWebdavLocks/exclusiveLocks.feature:97](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L97) +- [apiWebdavLocks/exclusiveLocks.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L102) +- [apiWebdavLocks/exclusiveLocks.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L103) +- [apiWebdavLocks/exclusiveLocks.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L120) +- [apiWebdavLocks/exclusiveLocks.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L121) +- [apiWebdavLocks/exclusiveLocks.feature:122](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L122) +- [apiWebdavLocks/exclusiveLocks.feature:123](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L123) +- [apiWebdavLocks/exclusiveLocks.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L128) +- [apiWebdavLocks/exclusiveLocks.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L129) +- [apiWebdavLocks/exclusiveLocks.feature:147](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L147) +- [apiWebdavLocks/exclusiveLocks.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L148) +- [apiWebdavLocks/exclusiveLocks.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L149) +- [apiWebdavLocks/exclusiveLocks.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L150) +- [apiWebdavLocks/exclusiveLocks.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L155) - [apiWebdavLocks/exclusiveLocks.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L156) -- [apiWebdavLocks/exclusiveLocks.feature:157](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L157) -- [apiWebdavLocks/exclusiveLocks.feature:158](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L158) -- [apiWebdavLocks/exclusiveLocks.feature:159](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L159) +- [apiWebdavLocks/exclusiveLocks.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L174) +- [apiWebdavLocks/exclusiveLocks.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L175) +- [apiWebdavLocks/exclusiveLocks.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L176) +- [apiWebdavLocks/exclusiveLocks.feature:177](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L177) +- [apiWebdavLocks/exclusiveLocks.feature:182](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L182) +- [apiWebdavLocks/exclusiveLocks.feature:183](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L183) +- [apiWebdavLocks/exclusiveLocks.feature:201](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L201) +- [apiWebdavLocks/exclusiveLocks.feature:202](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L202) +- [apiWebdavLocks/exclusiveLocks.feature:203](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L203) +- [apiWebdavLocks/exclusiveLocks.feature:204](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L204) +- [apiWebdavLocks/exclusiveLocks.feature:209](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L209) +- [apiWebdavLocks/exclusiveLocks.feature:210](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L210) - [apiWebdavLocks/folder.feature:18](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L18) - [apiWebdavLocks/folder.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L19) - [apiWebdavLocks/folder.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L20) - [apiWebdavLocks/folder.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L21) -- [apiWebdavLocks/folder.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L34) -- [apiWebdavLocks/folder.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L35) -- [apiWebdavLocks/folder.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L36) -- [apiWebdavLocks/folder.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L37) +- [apiWebdavLocks/folder.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L26) +- [apiWebdavLocks/folder.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L27) +- [apiWebdavLocks/folder.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L41) +- [apiWebdavLocks/folder.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L42) +- [apiWebdavLocks/folder.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L43) +- [apiWebdavLocks/folder.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L44) +- [apiWebdavLocks/folder.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L49) - [apiWebdavLocks/folder.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L50) -- [apiWebdavLocks/folder.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L51) -- [apiWebdavLocks/folder.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L52) -- [apiWebdavLocks/folder.feature:53](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L53) +- [apiWebdavLocks/folder.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L63) +- [apiWebdavLocks/folder.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L64) +- [apiWebdavLocks/folder.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L65) - [apiWebdavLocks/folder.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L66) -- [apiWebdavLocks/folder.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L67) -- [apiWebdavLocks/folder.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L68) -- [apiWebdavLocks/folder.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L69) -- [apiWebdavLocks/folder.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L83) -- [apiWebdavLocks/folder.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L84) -- [apiWebdavLocks/folder.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L85) +- [apiWebdavLocks/folder.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L71) +- [apiWebdavLocks/folder.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L72) - [apiWebdavLocks/folder.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L86) -- [apiWebdavLocks/folder.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L101) -- [apiWebdavLocks/folder.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L102) -- [apiWebdavLocks/folder.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L103) -- [apiWebdavLocks/folder.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L104) +- [apiWebdavLocks/folder.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L87) +- [apiWebdavLocks/folder.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L88) +- [apiWebdavLocks/folder.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L89) +- [apiWebdavLocks/folder.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L94) +- [apiWebdavLocks/folder.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L95) +- [apiWebdavLocks/folder.feature:110](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L110) +- [apiWebdavLocks/folder.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L111) +- [apiWebdavLocks/folder.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L112) +- [apiWebdavLocks/folder.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L113) +- [apiWebdavLocks/folder.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L118) - [apiWebdavLocks/folder.feature:119](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L119) -- [apiWebdavLocks/folder.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L120) -- [apiWebdavLocks/folder.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L121) -- [apiWebdavLocks/folder.feature:122](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L122) +- [apiWebdavLocks/folder.feature:135](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L135) +- [apiWebdavLocks/folder.feature:136](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L136) +- [apiWebdavLocks/folder.feature:137](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L137) +- [apiWebdavLocks/folder.feature:138](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L138) +- [apiWebdavLocks/folder.feature:144](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L144) +- [apiWebdavLocks/folder.feature:143](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L143) +- [apiWebdavLocks/folder.feature:160](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L160) +- [apiWebdavLocks/folder.feature:161](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L161) +- [apiWebdavLocks/folder.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L162) +- [apiWebdavLocks/folder.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L163) +- [apiWebdavLocks/folder.feature:168](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L168) +- [apiWebdavLocks/folder.feature:169](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L169) - [apiWebdavLocks/publicLink.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L32) - [apiWebdavLocks/publicLink.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L33) - [apiWebdavLocks/publicLink.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L34) - [apiWebdavLocks/publicLink.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L35) -- [apiWebdavLocks/publicLink.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L49) -- [apiWebdavLocks/publicLink.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L50) -- [apiWebdavLocks/publicLink.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L67) -- [apiWebdavLocks/publicLink.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L68) -- [apiWebdavLocks/publicLink.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L87) -- [apiWebdavLocks/publicLink.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L88) -- [apiWebdavLocks/publicLink.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L107) -- [apiWebdavLocks/publicLink.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L108) +- [apiWebdavLocks/publicLink.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L40) +- [apiWebdavLocks/publicLink.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L41) +- [apiWebdavLocks/publicLink.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L42) +- [apiWebdavLocks/publicLink.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L43) +- [apiWebdavLocks/publicLink.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L59) +- [apiWebdavLocks/publicLink.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L60) +- [apiWebdavLocks/publicLink.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L77) +- [apiWebdavLocks/publicLink.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L78) +- [apiWebdavLocks/publicLink.feature:97](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L97) +- [apiWebdavLocks/publicLink.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L98) +- [apiWebdavLocks/publicLink.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L116) +- [apiWebdavLocks/publicLink.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L117) - [apiWebdavLocks/publicLinkLockdiscovery.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L22) - [apiWebdavLocks/publicLinkLockdiscovery.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L23) - [apiWebdavLocks/publicLinkLockdiscovery.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L37) @@ -130,18 +164,21 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks/publicLinkLockdiscovery.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L98) - [apiWebdavLocks/publicLinkLockdiscovery.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L112) - [apiWebdavLocks/publicLinkLockdiscovery.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L113) -- [apiWebdavLocks/requestsWithToken.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L61) -- [apiWebdavLocks/requestsWithToken.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L62) -- [apiWebdavLocks/requestsWithToken.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L63) -- [apiWebdavLocks/requestsWithToken.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L64) +- [apiWebdavLocks/requestsWithToken.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L73) +- [apiWebdavLocks/requestsWithToken.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L74) +- [apiWebdavLocks/requestsWithToken.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L75) +- [apiWebdavLocks/requestsWithToken.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L76) - [apiWebdavLocks/requestsWithToken.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L81) - [apiWebdavLocks/requestsWithToken.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L82) -- [apiWebdavLocks/requestsWithToken.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L83) -- [apiWebdavLocks/requestsWithToken.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L84) -- [apiWebdavLocks/requestsWithToken.feature:106](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L106) -- [apiWebdavLocks/requestsWithToken.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L107) -- [apiWebdavLocks/requestsWithToken.feature:130](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L130) +- [apiWebdavLocks/requestsWithToken.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L100) +- [apiWebdavLocks/requestsWithToken.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L101) +- [apiWebdavLocks/requestsWithToken.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L102) +- [apiWebdavLocks/requestsWithToken.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L103) - [apiWebdavLocks/requestsWithToken.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L131) +- [apiWebdavLocks/requestsWithToken.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L132) +- [apiWebdavLocks/requestsWithToken.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L108) +- [apiWebdavLocks/requestsWithToken.feature:109](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L109) +- [apiWebdavLocks/requestsWithToken.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L162) - [apiWebdavLocks2/resharedSharesToShares.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L33) - [apiWebdavLocks2/resharedSharesToShares.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L34) - [apiWebdavLocks2/resharedSharesToShares.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L35) @@ -892,39 +929,36 @@ _ocdav: api compatibility, return correct status code_ - [apiCapabilities/capabilitiesWithNormalUser.feature:11](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilitiesWithNormalUser.feature#L11) Scenario: getting default capabilities with normal user #### [REPORT request not implemented](https://github.com/owncloud/ocis/issues/1330) -Scenario Outline: search for entry by pattern - [apiWebdavOperations/search.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L42) - [apiWebdavOperations/search.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L43) -Scenario Outline: search for entries by only some letters from the middle of the entry name -- [apiWebdavOperations/search.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L58) -- [apiWebdavOperations/search.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L59) -Scenario Outline: search for files by extension -- [apiWebdavOperations/search.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L75) -- [apiWebdavOperations/search.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L76) -Scenario Outline: search with empty field -- [apiWebdavOperations/search.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L84) -- [apiWebdavOperations/search.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L85) -Scenario Outline: limit returned search entries +- [apiWebdavOperations/search.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L48) +- [apiWebdavOperations/search.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L64) +- [apiWebdavOperations/search.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L65) +- [apiWebdavOperations/search.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L70) +- [apiWebdavOperations/search.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L87) +- [apiWebdavOperations/search.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L88) +- [apiWebdavOperations/search.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L93) - [apiWebdavOperations/search.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L102) - [apiWebdavOperations/search.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L103) -Scenario Outline: limit returned search entries to only 1 entry -- [apiWebdavOperations/search.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L120) -- [apiWebdavOperations/search.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L121) -Scenario Outline: limit returned search entries to more entires than there are -- [apiWebdavOperations/search.feature:139](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L139) -- [apiWebdavOperations/search.feature:140](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L140) -Scenario Outline: report extra properties in search entries for a file -- [apiWebdavOperations/search.feature:166](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L166) -- [apiWebdavOperations/search.feature:167](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L167) -Scenario Outline: report extra properties in search entries for a folder -- [apiWebdavOperations/search.feature:192](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L192) -- [apiWebdavOperations/search.feature:193](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L193) -Scenario Outline: search for entry with emoji by pattern -- [apiWebdavOperations/search.feature:211](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L211) -- [apiWebdavOperations/search.feature:212](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L212) -- [apiWebdavOperations/search.feature:214](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L214) Scenario: search for entry by tags using REPORT method -- [apiWebdavOperations/search.feature:230](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L230) Scenario: share a tagged resource to another internal user and sharee searches for tag using REPORT method -- [apiWebdavOperations/search.feature:255](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L255) Scenario: search for entries across various folders by tags using REPORT method +- [apiWebdavOperations/search.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L108) +- [apiWebdavOperations/search.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L126) +- [apiWebdavOperations/search.feature:127](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L127) +- [apiWebdavOperations/search.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L132) +- [apiWebdavOperations/search.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L150) +- [apiWebdavOperations/search.feature:151](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L151) +- [apiWebdavOperations/search.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L156) +- [apiWebdavOperations/search.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L174) +- [apiWebdavOperations/search.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L175) +- [apiWebdavOperations/search.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L180) +- [apiWebdavOperations/search.feature:207](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L207) +- [apiWebdavOperations/search.feature:208](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L208) +- [apiWebdavOperations/search.feature:213](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L213) +- [apiWebdavOperations/search.feature:239](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L239) +- [apiWebdavOperations/search.feature:240](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L240) +- [apiWebdavOperations/search.feature:245](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L245) +- [apiWebdavOperations/search.feature:264](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L264) +- [apiWebdavOperations/search.feature:265](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L265) +- [apiWebdavOperations/search.feature:270](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L270) And other missing implementation of favorites - [apiFavorites/favorites.feature:158](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L158) @@ -1019,12 +1053,11 @@ And other missing implementation of favorites - [apiWebdavEtagPropagation2/upload.feature:171](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L171) #### [WWW-Authenticate header for unauthenticated requests is not clear](https://github.com/owncloud/ocis/issues/2285) -Scenario Outline: Unauthenticated call -- [apiWebdavOperations/refuseAccess.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L21) - [apiWebdavOperations/refuseAccess.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L22) -Scenario Outline: A disabled user cannot use webdav -- [apiWebdavOperations/refuseAccess.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L34) +- [apiWebdavOperations/refuseAccess.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L23) - [apiWebdavOperations/refuseAccess.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L35) +- [apiWebdavOperations/refuseAccess.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L36) +- [apiWebdavOperations/refuseAccess.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L41) #### [wildcard Access-Control-Allow-Origin](https://github.com/owncloud/ocis/issues/1340) - [apiAuth/cors.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L24) @@ -1155,7 +1188,7 @@ Scenario Outline: A disabled user cannot use webdav - [apiCapabilities/capabilities.feature:737](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L737) - [apiCapabilities/capabilities.feature:766](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L766) - [apiCapabilities/capabilities.feature:795](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L795) -- [apiCapabilities/capabilities.feature:827](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L827) +- [apiCapabilities/capabilities.feature:827](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilities.feature#L827) - [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L25) - [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L26) - [apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares1/createShareWhenExcludedFromSharing.feature#L44) @@ -1186,6 +1219,10 @@ Scenario Outline: A disabled user cannot use webdav - [apiTranslation/translation.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L26) - [apiTranslation/translation.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L27) - [apiTranslation/translation.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L28) +- [apiTranslation/translation.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L33) +- [apiTranslation/translation.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L34) +- [apiTranslation/translation.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L35) +- [apiTranslation/translation.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L36) #### [Sharing a same file twice to the same group](https://github.com/owncloud/ocis/issues/1710) - [apiShareManagementBasicToShares/createShareToSharesFolder.feature:718](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L718) @@ -1232,18 +1269,24 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers #### [cannot set blacklisted file names](https://github.com/owncloud/product/issues/260) - [apiWebdavMove1/moveFolderToBlacklistedName.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L21) - [apiWebdavMove1/moveFolderToBlacklistedName.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L22) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L35) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L36) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L71) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L72) +- [apiWebdavMove1/moveFolderToBlacklistedName.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L27) +- [apiWebdavMove1/moveFolderToBlacklistedName.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L40) +- [apiWebdavMove1/moveFolderToBlacklistedName.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L41) +- [apiWebdavMove1/moveFolderToBlacklistedName.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L46) +- [apiWebdavMove1/moveFolderToBlacklistedName.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L81) +- [apiWebdavMove1/moveFolderToBlacklistedName.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L82) +- [apiWebdavMove1/moveFolderToBlacklistedName.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L87) #### [cannot set excluded directories](https://github.com/owncloud/product/issues/261) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L21) - [apiWebdavMove1/moveFolderToExcludedDirectory.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L22) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L35) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L36) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L72) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L73) +- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L23) +- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L28) +- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L42) +- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L43) +- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L48) +- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L84) +- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L85) +- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L90) #### [cannot set blacklisted file names](https://github.com/owncloud/product/issues/260) - [apiWebdavMove2/moveFileToBlacklistedName.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToBlacklistedName.feature#L19) @@ -1427,8 +1470,9 @@ _ocs: api compatibility, return correct status code_ - [apiShareOperationsToShares2/shareAccessByID.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L125) ### [Content-type is not multipart/byteranges when downloading file with Range Header](https://github.com/owncloud/ocis/issues/2677) -- [apiWebdavOperations/downloadFile.feature:169](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/downloadFile.feature#L169) -- [apiWebdavOperations/downloadFile.feature:170](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/downloadFile.feature#L170) +- [apiWebdavOperations/downloadFile.feature:222](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/downloadFile.feature#L222) +- [apiWebdavOperations/downloadFile.feature:223](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/downloadFile.feature#L223) +- [apiWebdavOperations/downloadFile.feature:228](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/downloadFile.feature#L228) #### [Creating a new folder which is a substring of Shares leads to Unknown Error](https://github.com/owncloud/ocis/issues/3033) - [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L79) @@ -1455,5 +1499,23 @@ _ocs: api compatibility, return correct status code_ - [apiWebdavUploadTUS/uploadFileMtimeShares.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFileMtimeShares.feature#L90) - [apiWebdavUploadTUS/uploadToShare.feature:341](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L341) +#### [Renaming resource to banned name is allowed in spaces webdav](https://github.com/owncloud/ocis/issues/3099) +- [apiWebdavMove1/moveFolder.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolder.feature#L27) +- [apiWebdavMove1/moveFolder.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolder.feature#L45) +- [apiWebdavMove1/moveFolder.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolder.feature#L63) + +#### [REPORT method on spaces returns an incorrect d:href response](https://github.com/owncloud/ocis/issues/3111) +- [apiFavorites/favorites.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L117) +- [apiFavorites/favorites.feature:143](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L143) +- [apiFavorites/favorites.feature:268](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L268) + +#### [could not create system tag](https://github.com/owncloud/ocis/issues/3092) +- [apiWebdavOperations/search.feature:273](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L273) +- [apiWebdavOperations/search.feature:289](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L289) +- [apiWebdavOperations/search.feature:314](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L314) + +#### [Incorrect response while listing resources of a folder with depth infinity](https://github.com/owncloud/ocis/issues/3073) +- [apiWebdavOperations/listFiles.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L128) + Note: always have an empty line at the end of this file. The bash script that processes this file may not process a scenario reference on the last line. diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index c3bb9e212c3..6457f7e5cbb 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -62,74 +62,108 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks/exclusiveLocks.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L19) - [apiWebdavLocks/exclusiveLocks.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L20) - [apiWebdavLocks/exclusiveLocks.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L21) -- [apiWebdavLocks/exclusiveLocks.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L36) -- [apiWebdavLocks/exclusiveLocks.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L37) -- [apiWebdavLocks/exclusiveLocks.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L38) -- [apiWebdavLocks/exclusiveLocks.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L39) -- [apiWebdavLocks/exclusiveLocks.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L55) -- [apiWebdavLocks/exclusiveLocks.feature:56](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L56) -- [apiWebdavLocks/exclusiveLocks.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L57) -- [apiWebdavLocks/exclusiveLocks.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L58) -- [apiWebdavLocks/exclusiveLocks.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L74) -- [apiWebdavLocks/exclusiveLocks.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L75) -- [apiWebdavLocks/exclusiveLocks.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L76) +- [apiWebdavLocks/exclusiveLocks.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L26) +- [apiWebdavLocks/exclusiveLocks.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L27) +- [apiWebdavLocks/exclusiveLocks.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L43) +- [apiWebdavLocks/exclusiveLocks.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L44) +- [apiWebdavLocks/exclusiveLocks.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L45) +- [apiWebdavLocks/exclusiveLocks.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L46) +- [apiWebdavLocks/exclusiveLocks.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L51) +- [apiWebdavLocks/exclusiveLocks.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L52) +- [apiWebdavLocks/exclusiveLocks.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L69) +- [apiWebdavLocks/exclusiveLocks.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L70) +- [apiWebdavLocks/exclusiveLocks.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L71) +- [apiWebdavLocks/exclusiveLocks.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L72) - [apiWebdavLocks/exclusiveLocks.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L77) -- [apiWebdavLocks/exclusiveLocks.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L93) +- [apiWebdavLocks/exclusiveLocks.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L78) - [apiWebdavLocks/exclusiveLocks.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L94) - [apiWebdavLocks/exclusiveLocks.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L95) - [apiWebdavLocks/exclusiveLocks.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L96) -- [apiWebdavLocks/exclusiveLocks.feature:114](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L114) -- [apiWebdavLocks/exclusiveLocks.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L115) -- [apiWebdavLocks/exclusiveLocks.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L116) -- [apiWebdavLocks/exclusiveLocks.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L117) -- [apiWebdavLocks/exclusiveLocks.feature:135](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L135) -- [apiWebdavLocks/exclusiveLocks.feature:136](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L136) -- [apiWebdavLocks/exclusiveLocks.feature:137](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L137) -- [apiWebdavLocks/exclusiveLocks.feature:138](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L138) +- [apiWebdavLocks/exclusiveLocks.feature:97](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L97) +- [apiWebdavLocks/exclusiveLocks.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L102) +- [apiWebdavLocks/exclusiveLocks.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L103) +- [apiWebdavLocks/exclusiveLocks.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L120) +- [apiWebdavLocks/exclusiveLocks.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L121) +- [apiWebdavLocks/exclusiveLocks.feature:122](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L122) +- [apiWebdavLocks/exclusiveLocks.feature:123](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L123) +- [apiWebdavLocks/exclusiveLocks.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L128) +- [apiWebdavLocks/exclusiveLocks.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L129) +- [apiWebdavLocks/exclusiveLocks.feature:147](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L147) +- [apiWebdavLocks/exclusiveLocks.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L148) +- [apiWebdavLocks/exclusiveLocks.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L149) +- [apiWebdavLocks/exclusiveLocks.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L150) +- [apiWebdavLocks/exclusiveLocks.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L155) - [apiWebdavLocks/exclusiveLocks.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L156) -- [apiWebdavLocks/exclusiveLocks.feature:157](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L157) -- [apiWebdavLocks/exclusiveLocks.feature:158](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L158) -- [apiWebdavLocks/exclusiveLocks.feature:159](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L159) +- [apiWebdavLocks/exclusiveLocks.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L174) +- [apiWebdavLocks/exclusiveLocks.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L175) +- [apiWebdavLocks/exclusiveLocks.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L176) +- [apiWebdavLocks/exclusiveLocks.feature:177](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L177) +- [apiWebdavLocks/exclusiveLocks.feature:182](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L182) +- [apiWebdavLocks/exclusiveLocks.feature:183](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L183) +- [apiWebdavLocks/exclusiveLocks.feature:201](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L201) +- [apiWebdavLocks/exclusiveLocks.feature:202](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L202) +- [apiWebdavLocks/exclusiveLocks.feature:203](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L203) +- [apiWebdavLocks/exclusiveLocks.feature:204](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L204) +- [apiWebdavLocks/exclusiveLocks.feature:209](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L209) +- [apiWebdavLocks/exclusiveLocks.feature:210](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L210) - [apiWebdavLocks/folder.feature:18](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L18) - [apiWebdavLocks/folder.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L19) - [apiWebdavLocks/folder.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L20) - [apiWebdavLocks/folder.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L21) -- [apiWebdavLocks/folder.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L34) -- [apiWebdavLocks/folder.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L35) -- [apiWebdavLocks/folder.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L36) -- [apiWebdavLocks/folder.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L37) +- [apiWebdavLocks/folder.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L26) +- [apiWebdavLocks/folder.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L27) +- [apiWebdavLocks/folder.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L41) +- [apiWebdavLocks/folder.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L42) +- [apiWebdavLocks/folder.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L43) +- [apiWebdavLocks/folder.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L44) +- [apiWebdavLocks/folder.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L49) - [apiWebdavLocks/folder.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L50) -- [apiWebdavLocks/folder.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L51) -- [apiWebdavLocks/folder.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L52) -- [apiWebdavLocks/folder.feature:53](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L53) +- [apiWebdavLocks/folder.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L63) +- [apiWebdavLocks/folder.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L64) +- [apiWebdavLocks/folder.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L65) - [apiWebdavLocks/folder.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L66) -- [apiWebdavLocks/folder.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L67) -- [apiWebdavLocks/folder.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L68) -- [apiWebdavLocks/folder.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L69) -- [apiWebdavLocks/folder.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L83) -- [apiWebdavLocks/folder.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L84) -- [apiWebdavLocks/folder.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L85) +- [apiWebdavLocks/folder.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L71) +- [apiWebdavLocks/folder.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L72) - [apiWebdavLocks/folder.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L86) -- [apiWebdavLocks/folder.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L101) -- [apiWebdavLocks/folder.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L102) -- [apiWebdavLocks/folder.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L103) -- [apiWebdavLocks/folder.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L104) +- [apiWebdavLocks/folder.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L87) +- [apiWebdavLocks/folder.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L88) +- [apiWebdavLocks/folder.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L89) +- [apiWebdavLocks/folder.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L94) +- [apiWebdavLocks/folder.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L95) +- [apiWebdavLocks/folder.feature:110](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L110) +- [apiWebdavLocks/folder.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L111) +- [apiWebdavLocks/folder.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L112) +- [apiWebdavLocks/folder.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L113) +- [apiWebdavLocks/folder.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L118) - [apiWebdavLocks/folder.feature:119](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L119) -- [apiWebdavLocks/folder.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L120) -- [apiWebdavLocks/folder.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L121) -- [apiWebdavLocks/folder.feature:122](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L122) +- [apiWebdavLocks/folder.feature:135](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L135) +- [apiWebdavLocks/folder.feature:136](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L136) +- [apiWebdavLocks/folder.feature:137](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L137) +- [apiWebdavLocks/folder.feature:138](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L138) +- [apiWebdavLocks/folder.feature:144](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L144) +- [apiWebdavLocks/folder.feature:143](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L143) +- [apiWebdavLocks/folder.feature:160](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L160) +- [apiWebdavLocks/folder.feature:161](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L161) +- [apiWebdavLocks/folder.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L162) +- [apiWebdavLocks/folder.feature:163](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L163) +- [apiWebdavLocks/folder.feature:168](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L168) +- [apiWebdavLocks/folder.feature:169](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/folder.feature#L169) - [apiWebdavLocks/publicLink.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L32) - [apiWebdavLocks/publicLink.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L33) - [apiWebdavLocks/publicLink.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L34) - [apiWebdavLocks/publicLink.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L35) -- [apiWebdavLocks/publicLink.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L49) -- [apiWebdavLocks/publicLink.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L50) -- [apiWebdavLocks/publicLink.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L67) -- [apiWebdavLocks/publicLink.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L68) -- [apiWebdavLocks/publicLink.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L87) -- [apiWebdavLocks/publicLink.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L88) -- [apiWebdavLocks/publicLink.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L107) -- [apiWebdavLocks/publicLink.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L108) +- [apiWebdavLocks/publicLink.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L40) +- [apiWebdavLocks/publicLink.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L41) +- [apiWebdavLocks/publicLink.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L42) +- [apiWebdavLocks/publicLink.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L43) +- [apiWebdavLocks/publicLink.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L59) +- [apiWebdavLocks/publicLink.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L60) +- [apiWebdavLocks/publicLink.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L77) +- [apiWebdavLocks/publicLink.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L78) +- [apiWebdavLocks/publicLink.feature:97](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L97) +- [apiWebdavLocks/publicLink.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L98) +- [apiWebdavLocks/publicLink.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L116) +- [apiWebdavLocks/publicLink.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLink.feature#L117) - [apiWebdavLocks/publicLinkLockdiscovery.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L22) - [apiWebdavLocks/publicLinkLockdiscovery.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L23) - [apiWebdavLocks/publicLinkLockdiscovery.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L37) @@ -144,18 +178,21 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks/publicLinkLockdiscovery.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L98) - [apiWebdavLocks/publicLinkLockdiscovery.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L112) - [apiWebdavLocks/publicLinkLockdiscovery.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/publicLinkLockdiscovery.feature#L113) -- [apiWebdavLocks/requestsWithToken.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L61) -- [apiWebdavLocks/requestsWithToken.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L62) -- [apiWebdavLocks/requestsWithToken.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L63) -- [apiWebdavLocks/requestsWithToken.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L64) +- [apiWebdavLocks/requestsWithToken.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L73) +- [apiWebdavLocks/requestsWithToken.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L74) +- [apiWebdavLocks/requestsWithToken.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L75) +- [apiWebdavLocks/requestsWithToken.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L76) - [apiWebdavLocks/requestsWithToken.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L81) - [apiWebdavLocks/requestsWithToken.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L82) -- [apiWebdavLocks/requestsWithToken.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L83) -- [apiWebdavLocks/requestsWithToken.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L84) -- [apiWebdavLocks/requestsWithToken.feature:106](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L106) -- [apiWebdavLocks/requestsWithToken.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L107) -- [apiWebdavLocks/requestsWithToken.feature:130](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L130) +- [apiWebdavLocks/requestsWithToken.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L100) +- [apiWebdavLocks/requestsWithToken.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L101) +- [apiWebdavLocks/requestsWithToken.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L102) +- [apiWebdavLocks/requestsWithToken.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L103) - [apiWebdavLocks/requestsWithToken.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L131) +- [apiWebdavLocks/requestsWithToken.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L132) +- [apiWebdavLocks/requestsWithToken.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L108) +- [apiWebdavLocks/requestsWithToken.feature:109](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L109) +- [apiWebdavLocks/requestsWithToken.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L162) - [apiWebdavLocks2/resharedSharesToShares.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L33) - [apiWebdavLocks2/resharedSharesToShares.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L34) - [apiWebdavLocks2/resharedSharesToShares.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L35) @@ -909,39 +946,36 @@ _ocdav: api compatibility, return correct status code_ - [apiCapabilities/capabilitiesWithNormalUser.feature:11](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiCapabilities/capabilitiesWithNormalUser.feature#L11) Scenario: getting default capabilities with normal user #### [REPORT request not implemented](https://github.com/owncloud/ocis/issues/1330) -Scenario Outline: search for entry by pattern - [apiWebdavOperations/search.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L42) - [apiWebdavOperations/search.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L43) -Scenario Outline: search for entries by only some letters from the middle of the entry name -- [apiWebdavOperations/search.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L58) -- [apiWebdavOperations/search.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L59) -Scenario Outline: search for files by extension -- [apiWebdavOperations/search.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L75) -- [apiWebdavOperations/search.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L76) -Scenario Outline: search with empty field -- [apiWebdavOperations/search.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L84) -- [apiWebdavOperations/search.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L85) -Scenario Outline: limit returned search entries +- [apiWebdavOperations/search.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L48) +- [apiWebdavOperations/search.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L64) +- [apiWebdavOperations/search.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L65) +- [apiWebdavOperations/search.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L70) +- [apiWebdavOperations/search.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L87) +- [apiWebdavOperations/search.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L88) +- [apiWebdavOperations/search.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L93) - [apiWebdavOperations/search.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L102) - [apiWebdavOperations/search.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L103) -Scenario Outline: limit returned search entries to only 1 entry -- [apiWebdavOperations/search.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L120) -- [apiWebdavOperations/search.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L121) -Scenario Outline: limit returned search entries to more entires than there are -- [apiWebdavOperations/search.feature:139](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L139) -- [apiWebdavOperations/search.feature:140](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L140) -Scenario Outline: report extra properties in search entries for a file -- [apiWebdavOperations/search.feature:166](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L166) -- [apiWebdavOperations/search.feature:167](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L167) -Scenario Outline: report extra properties in search entries for a folder -- [apiWebdavOperations/search.feature:192](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L192) -- [apiWebdavOperations/search.feature:193](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L193) -Scenario Outline: search for entry with emoji by pattern -- [apiWebdavOperations/search.feature:211](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L211) -- [apiWebdavOperations/search.feature:212](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L212) -- [apiWebdavOperations/search.feature:214](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L214) Scenario: search for entry by tags using REPORT method -- [apiWebdavOperations/search.feature:230](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L230) Scenario: share a tagged resource to another internal user and sharee searches for tag using REPORT method -- [apiWebdavOperations/search.feature:255](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L255) Scenario: search for entries across various folders by tags using REPORT method +- [apiWebdavOperations/search.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L108) +- [apiWebdavOperations/search.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L126) +- [apiWebdavOperations/search.feature:127](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L127) +- [apiWebdavOperations/search.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L132) +- [apiWebdavOperations/search.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L150) +- [apiWebdavOperations/search.feature:151](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L151) +- [apiWebdavOperations/search.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L156) +- [apiWebdavOperations/search.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L174) +- [apiWebdavOperations/search.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L175) +- [apiWebdavOperations/search.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L180) +- [apiWebdavOperations/search.feature:207](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L207) +- [apiWebdavOperations/search.feature:208](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L208) +- [apiWebdavOperations/search.feature:213](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L213) +- [apiWebdavOperations/search.feature:239](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L239) +- [apiWebdavOperations/search.feature:240](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L240) +- [apiWebdavOperations/search.feature:245](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L245) +- [apiWebdavOperations/search.feature:264](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L264) +- [apiWebdavOperations/search.feature:265](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L265) +- [apiWebdavOperations/search.feature:270](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L270) And other missing implementation of favorites - [apiFavorites/favorites.feature:158](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L158) @@ -1036,12 +1070,11 @@ And other missing implementation of favorites - [apiWebdavEtagPropagation2/upload.feature:171](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L171) #### [WWW-Authenticate header for unauthenticated requests is not clear](https://github.com/owncloud/ocis/issues/2285) -Scenario Outline: Unauthenticated call -- [apiWebdavOperations/refuseAccess.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L21) - [apiWebdavOperations/refuseAccess.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L22) -Scenario Outline: A disabled user cannot use webdav -- [apiWebdavOperations/refuseAccess.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L34) +- [apiWebdavOperations/refuseAccess.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L23) - [apiWebdavOperations/refuseAccess.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L35) +- [apiWebdavOperations/refuseAccess.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L36) +- [apiWebdavOperations/refuseAccess.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L41) #### [wildcard Access-Control-Allow-Origin](https://github.com/owncloud/ocis/issues/1340) - [apiAuth/cors.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiAuth/cors.feature#L24) @@ -1203,6 +1236,10 @@ Scenario Outline: A disabled user cannot use webdav - [apiTranslation/translation.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L26) - [apiTranslation/translation.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L27) - [apiTranslation/translation.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L28) +- [apiTranslation/translation.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L33) +- [apiTranslation/translation.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L34) +- [apiTranslation/translation.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L35) +- [apiTranslation/translation.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTranslation/translation.feature#L36) #### [Sharing a same file twice to the same group](https://github.com/owncloud/ocis/issues/1710) - [apiShareManagementBasicToShares/createShareToSharesFolder.feature:718](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L718) @@ -1249,18 +1286,24 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers #### [cannot set blacklisted file names](https://github.com/owncloud/product/issues/260) - [apiWebdavMove1/moveFolderToBlacklistedName.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L21) - [apiWebdavMove1/moveFolderToBlacklistedName.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L22) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L35) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L36) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L71) -- [apiWebdavMove1/moveFolderToBlacklistedName.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L72) +- [apiWebdavMove1/moveFolderToBlacklistedName.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L27) +- [apiWebdavMove1/moveFolderToBlacklistedName.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L40) +- [apiWebdavMove1/moveFolderToBlacklistedName.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L41) +- [apiWebdavMove1/moveFolderToBlacklistedName.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L46) +- [apiWebdavMove1/moveFolderToBlacklistedName.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L81) +- [apiWebdavMove1/moveFolderToBlacklistedName.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L82) +- [apiWebdavMove1/moveFolderToBlacklistedName.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToBlacklistedName.feature#L87) #### [cannot set excluded directories](https://github.com/owncloud/product/issues/261) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L21) - [apiWebdavMove1/moveFolderToExcludedDirectory.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L22) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L35) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L36) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L72) -- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L73) +- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:23](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L23) +- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L28) +- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L42) +- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L43) +- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L48) +- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L84) +- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L85) +- [apiWebdavMove1/moveFolderToExcludedDirectory.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolderToExcludedDirectory.feature#L90) #### [cannot set blacklisted file names](https://github.com/owncloud/product/issues/260) - [apiWebdavMove2/moveFileToBlacklistedName.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFileToBlacklistedName.feature#L19) @@ -1428,8 +1471,9 @@ _ocs: api compatibility, return correct status code_ - [apiShareOperationsToShares2/shareAccessByID.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares2/shareAccessByID.feature#L125) ### [Content-type is not multipart/byteranges when downloading file with Range Header](https://github.com/owncloud/ocis/issues/2677) -- [apiWebdavOperations/downloadFile.feature:169](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/downloadFile.feature#L169) -- [apiWebdavOperations/downloadFile.feature:170](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/downloadFile.feature#L170) +- [apiWebdavOperations/downloadFile.feature:222](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/downloadFile.feature#L222) +- [apiWebdavOperations/downloadFile.feature:223](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/downloadFile.feature#L223) +- [apiWebdavOperations/downloadFile.feature:228](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/downloadFile.feature#L228) #### [Creating a new folder which is a substring of Shares leads to Unknown Error](https://github.com/owncloud/ocis/issues/3033) - [apiWebdavProperties1/createFileFolderWhenSharesExist.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFileFolderWhenSharesExist.feature#L79) @@ -1456,5 +1500,23 @@ _ocs: api compatibility, return correct status code_ - [apiWebdavUploadTUS/uploadFileMtimeShares.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadFileMtimeShares.feature#L90) - [apiWebdavUploadTUS/uploadToShare.feature:341](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUploadTUS/uploadToShare.feature#L341) -Note: always have an empty line at the end of this file. +#### [Renaming resource to banned name is allowed in spaces webdav](https://github.com/owncloud/ocis/issues/3099) +- [apiWebdavMove1/moveFolder.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolder.feature#L27) +- [apiWebdavMove1/moveFolder.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolder.feature#L45) +- [apiWebdavMove1/moveFolder.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove1/moveFolder.feature#L63) + +#### [REPORT method on spaces returns an incorrect d:href response](https://github.com/owncloud/ocis/issues/3111) +- [apiFavorites/favorites.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L117) +- [apiFavorites/favorites.feature:143](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L143) +- [apiFavorites/favorites.feature:268](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L268) + +#### [could not create system tag](https://github.com/owncloud/ocis/issues/3092) +- [apiWebdavOperations/search.feature:273](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L273) +- [apiWebdavOperations/search.feature:289](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L289) +- [apiWebdavOperations/search.feature:314](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L314) + +#### [Incorrect response while listing resources of a folder with depth infinity](https://github.com/owncloud/ocis/issues/3073) +- [apiWebdavOperations/listFiles.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L128) + + Note: always have an empty line at the end of this file. The bash script that processes this file may not process a scenario reference on the last line. From 0814d2f50559793cf2cba8058c93130b26d126e6 Mon Sep 17 00:00:00 2001 From: Michael Barz Date: Tue, 8 Feb 2022 14:17:16 +0100 Subject: [PATCH 40/49] remove creation of .space folder (#2519) * remove creation of .space folder * fix unit tests * fix integration tests --- changelog/unreleased/remove-dot-space.md | 5 +++++ pkg/storage/utils/decomposedfs/spaces.go | 17 ----------------- pkg/storage/utils/decomposedfs/upload_test.go | 19 +++++++------------ .../grpc/gateway_storageprovider_test.go | 6 +++--- .../integration/grpc/storageprovider_test.go | 2 +- 5 files changed, 16 insertions(+), 33 deletions(-) create mode 100644 changelog/unreleased/remove-dot-space.md diff --git a/changelog/unreleased/remove-dot-space.md b/changelog/unreleased/remove-dot-space.md new file mode 100644 index 00000000000..fb69cac1eae --- /dev/null +++ b/changelog/unreleased/remove-dot-space.md @@ -0,0 +1,5 @@ +Change: remove the auto creation of the .space folder + +We removed the auto creation of the .space folder because we don't develop this feature further. + +https://github.com/cs3org/reva/pull/2519 diff --git a/pkg/storage/utils/decomposedfs/spaces.go b/pkg/storage/utils/decomposedfs/spaces.go index 1a6bcee6b56..d26a3581073 100644 --- a/pkg/storage/utils/decomposedfs/spaces.go +++ b/pkg/storage/utils/decomposedfs/spaces.go @@ -99,10 +99,6 @@ func (fs *Decomposedfs) CreateStorageSpace(ctx context.Context, req *provider.Cr return nil, err } - if err := fs.createHiddenSpaceFolder(ctx, n); err != nil { - return nil, err - } - u, ok := ctxpkg.ContextGetUser(ctx) if !ok { return nil, fmt.Errorf("decomposedfs: spaces: contextual user not found") @@ -498,19 +494,6 @@ func (fs *Decomposedfs) DeleteStorageSpace(ctx context.Context, req *provider.De return os.Symlink(trashPath, np) } -// createHiddenSpaceFolder bootstraps a storage space root with a hidden ".space" folder used to store space related -// metadata such as a description or an image. -// Internally createHiddenSpaceFolder leverages the use of node.Child() to create a new node under the space root. -// createHiddenSpaceFolder is just a contextual alias for node.Child() for ".spaces". -func (fs *Decomposedfs) createHiddenSpaceFolder(ctx context.Context, r *node.Node) error { - hiddenSpace, err := r.Child(ctx, ".space") - if err != nil { - return err - } - - return fs.tp.CreateDir(ctx, hiddenSpace) -} - func (fs *Decomposedfs) createStorageSpace(ctx context.Context, spaceType, spaceID string) error { // create space type dir if err := os.MkdirAll(filepath.Join(fs.o.Root, "spaces", spaceType), 0700); err != nil { diff --git a/pkg/storage/utils/decomposedfs/upload_test.go b/pkg/storage/utils/decomposedfs/upload_test.go index 1ad307b18f9..1db9dc21abd 100644 --- a/pkg/storage/utils/decomposedfs/upload_test.go +++ b/pkg/storage/utils/decomposedfs/upload_test.go @@ -189,8 +189,7 @@ var _ = Describe("File uploads", func() { resources, err := fs.ListFolder(ctx, rootRef, []string{}) Expect(err).ToNot(HaveOccurred()) - Expect(len(resources)).To(Equal(1)) // .space folder - Expect(resources[0].Path).To(Equal("/.space")) + Expect(len(resources)).To(Equal(0)) }) }) @@ -206,8 +205,7 @@ var _ = Describe("File uploads", func() { resources, err := fs.ListFolder(ctx, rootRef, []string{}) Expect(err).ToNot(HaveOccurred()) - Expect(len(resources)).To(Equal(1)) // .space folder - Expect(resources[0].Path).To(Equal("/.space")) + Expect(len(resources)).To(Equal(0)) }) }) @@ -244,9 +242,8 @@ var _ = Describe("File uploads", func() { resources, err := fs.ListFolder(ctx, rootRef, []string{}) Expect(err).ToNot(HaveOccurred()) - Expect(len(resources)).To(Equal(2)) // .space folder & uploaded resource - Expect(resources[0].Path).To(Or(Equal(ref.Path), Equal("/.space"))) - Expect(resources[1].Path).To(Or(Equal(ref.Path), Equal("/.space"))) + Expect(len(resources)).To(Equal(1)) + Expect(resources[0].Path).To(Equal(ref.Path)) }) }) @@ -283,9 +280,8 @@ var _ = Describe("File uploads", func() { resources, err := fs.ListFolder(ctx, rootRef, []string{}) Expect(err).ToNot(HaveOccurred()) - Expect(len(resources)).To(Equal(2)) // .space folder & uploaded file - Expect(resources[0].Path).To(Or(Equal(ref.Path), Equal("/.space"))) - Expect(resources[1].Path).To(Or(Equal(ref.Path), Equal("/.space"))) + Expect(len(resources)).To(Equal(1)) + Expect(resources[0].Path).To(Equal(ref.Path)) }) }) @@ -303,8 +299,7 @@ var _ = Describe("File uploads", func() { resources, err := fs.ListFolder(ctx, rootRef, []string{}) Expect(err).ToNot(HaveOccurred()) - Expect(len(resources)).To(Equal(1)) // .space folder - Expect(resources[0].Path).To(Equal("/.space")) + Expect(len(resources)).To(Equal(0)) }) }) diff --git a/tests/integration/grpc/gateway_storageprovider_test.go b/tests/integration/grpc/gateway_storageprovider_test.go index 2fac29bdc1b..af7df5ec76a 100644 --- a/tests/integration/grpc/gateway_storageprovider_test.go +++ b/tests/integration/grpc/gateway_storageprovider_test.go @@ -334,12 +334,12 @@ var _ = Describe("gateway", func() { listRes, err = serviceClient.ListContainer(ctx, &storagep.ListContainerRequest{Ref: &storagep.Reference{ResourceId: shard1Space.Root, Path: "."}}) Expect(err).ToNot(HaveOccurred()) Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) - Expect(len(listRes.Infos)).To(Equal(2)) + Expect(len(listRes.Infos)).To(Equal(1)) paths := []string{} for _, i := range listRes.Infos { paths = append(paths, i.Path) } - Expect(paths).To(ConsistOf([]string{"file.txt", ".space"})) + Expect(paths).To(ConsistOf([]string{"file.txt"})) }) }) @@ -429,7 +429,7 @@ var _ = Describe("gateway", func() { listRes, err := serviceClient.ListContainer(ctx, &storagep.ListContainerRequest{Ref: homeRef}) Expect(err).ToNot(HaveOccurred()) Expect(listRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) - Expect(len(listRes.Infos)).To(Equal(2)) + Expect(len(listRes.Infos)).To(Equal(1)) var fileInfo *storagep.ResourceInfo // var embeddedInfo *storagep.ResourceInfo diff --git a/tests/integration/grpc/storageprovider_test.go b/tests/integration/grpc/storageprovider_test.go index 2b005eaec57..8f9af283f17 100644 --- a/tests/integration/grpc/storageprovider_test.go +++ b/tests/integration/grpc/storageprovider_test.go @@ -175,7 +175,7 @@ var _ = Describe("storage providers", func() { switch provider { case "ocis": - Expect(len(listRes.Infos)).To(Equal(2)) // subdir + .space + Expect(len(listRes.Infos)).To(Equal(1)) // subdir case "nextcloud": Expect(len(listRes.Infos)).To(Equal(1)) // subdir default: From 924f4d2b07b5613ae72b452811751ec44b216afe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 9 Feb 2022 11:22:41 +0100 Subject: [PATCH 41/49] decomposedfs: add locking support (#2460) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * decomposedfs: add locking support Signed-off-by: Jörn Friedrich Dreyer * add lock implementation, refactor error handling Signed-off-by: Jörn Friedrich Dreyer * introduce lock ctx Signed-off-by: Jörn Friedrich Dreyer * add locked error and status code mapping Signed-off-by: Jörn Friedrich Dreyer * read lockid from opaque into ctx for delete Signed-off-by: Jörn Friedrich Dreyer * decomposedfs: make delete respect lock Signed-off-by: Jörn Friedrich Dreyer * ocdav: simplify error code mapping Signed-off-by: Jörn Friedrich Dreyer * adjust to cs3 lock api update Signed-off-by: Jörn Friedrich Dreyer * utils: add and read plain opaque entries Signed-off-by: Jörn Friedrich Dreyer * fix delete lock Signed-off-by: Jörn Friedrich Dreyer * invalidate stat cache when setting lock Signed-off-by: Jörn Friedrich Dreyer * fix a few linter items Signed-off-by: Jörn Friedrich Dreyer * linter happyness Signed-off-by: Jörn Friedrich Dreyer * ocdav: implment unlock Signed-off-by: Jörn Friedrich Dreyer * lock caching and unlocking Signed-off-by: Jörn Friedrich Dreyer * read locks on folders Signed-off-by: Jörn Friedrich Dreyer * check lock on writes Signed-off-by: Jörn Friedrich Dreyer * always assume locktype write Signed-off-by: Jörn Friedrich Dreyer * ocis only supports exclusive locks Signed-off-by: Jörn Friedrich Dreyer * add precodition failed errtype * handle preconditionfailed in status conversion * omit empty xml tags in lockdiscovery * handle locked status on LOCK * document oc10 lock behaviour as comment * ocdav: handle errors for LOCK and UNLOCK * storage: change fs.Unlock signature * decomposedfs: refactor checkLock * add LookupReferenceForPath comment Signed-off-by: Jörn Friedrich Dreyer * gateway: ignore unimplemented add/denyGrant response Signed-off-by: Jörn Friedrich Dreyer * use http.StatusMethodNotAllowed for mkcol error case Signed-off-by: Jörn Friedrich Dreyer * make hound happy Signed-off-by: Jörn Friedrich Dreyer * ocdav: be more tolerant with the Lock-Token header Signed-off-by: Jörn Friedrich Dreyer * ocdav: allow setting infinity timeout Signed-off-by: Jörn Friedrich Dreyer * ocdav: use custem owner innerxml without href * update expected failures * ocdav: return conflict on missing intermediate target dir Signed-off-by: Jörn Friedrich Dreyer * decomposedfs: use checkLock() in the rest of cases Signed-off-by: Jörn Friedrich Dreyer * Do not choke when checking the lock of non-existent nodes * Linter fixes * Refactor: Move lock handling into the node domain * Also delete the lock file when deleting a node * Extract node lock-handling code into separate file, start writing tests * Add missing license header * Fix relocking already-locked nodes. Increase test coverage * Hounds be happy * Add unit tests for ReadLock and RefreshLock * Also cover readLocksIntoOpaque in the tests * Fix linter issue * Do not log full nodes, it's very expensive and not very helpful * Start adding grpc integration tests for locking * Fix setting the lock for file uploads * Allow for locking space-based resources * Make sure to log the error before it's getting overwritten * reuse xml.EscapeText directly Co-authored-by: David Christofas * decomposedfs: use defer to close file when unlocking resource Signed-off-by: Jörn Friedrich Dreyer * ocdav: explain why some http states are commented Signed-off-by: Jörn Friedrich Dreyer * ocdav: use http header status constants Signed-off-by: Jörn Friedrich Dreyer * clarify add/deny grant log Signed-off-by: Jörn Friedrich Dreyer * update expected failures Signed-off-by: Jörn Friedrich Dreyer Co-authored-by: André Duffeck Co-authored-by: David Christofas --- changelog/unreleased/decomposedfs-locking.md | 5 + .../grpc/services/gateway/storageprovider.go | 3 + .../services/gateway/usershareprovider.go | 39 +- .../storageprovider/storageprovider.go | 540 ++++------------ internal/http/services/owncloud/ocdav/copy.go | 16 +- internal/http/services/owncloud/ocdav/dav.go | 10 +- .../http/services/owncloud/ocdav/delete.go | 78 ++- .../services/owncloud/ocdav/errors/error.go | 163 +++-- internal/http/services/owncloud/ocdav/if.go | 193 ++++++ .../http/services/owncloud/ocdav/if_test.go | 338 ++++++++++ internal/http/services/owncloud/ocdav/lock.go | 51 -- .../http/services/owncloud/ocdav/locks.go | 608 ++++++++++++++++++ .../http/services/owncloud/ocdav/mkcol.go | 8 +- internal/http/services/owncloud/ocdav/move.go | 4 +- .../services/owncloud/ocdav/net/headers.go | 16 +- .../http/services/owncloud/ocdav/ocdav.go | 17 +- .../owncloud/ocdav/propfind/propfind.go | 108 +++- .../http/services/owncloud/ocdav/proppatch.go | 8 +- .../services/owncloud/ocdav/props/props.go | 28 + internal/http/services/owncloud/ocdav/put.go | 4 +- .../owncloud/ocdav/spacelookup/spacelookup.go | 17 +- .../http/services/owncloud/ocdav/spaces.go | 40 +- .../http/services/owncloud/ocdav/trashbin.go | 15 +- internal/http/services/owncloud/ocdav/tus.go | 2 +- .../http/services/owncloud/ocdav/versions.go | 2 +- .../http/services/owncloud/ocdav/webdav.go | 35 +- .../ocdav/unlock.go => pkg/ctx/lockctx.go | 16 +- pkg/ctx/userctx.go | 1 + pkg/errtypes/errtypes.go | 51 +- pkg/rgrpc/status/status.go | 52 +- pkg/storage/fs/nextcloud/nextcloud.go | 2 +- pkg/storage/fs/owncloudsql/owncloudsql.go | 2 +- pkg/storage/fs/s3/s3.go | 2 +- pkg/storage/storage.go | 2 +- .../utils/decomposedfs/decomposedfs.go | 108 +++- pkg/storage/utils/decomposedfs/grants.go | 10 + pkg/storage/utils/decomposedfs/metadata.go | 10 + pkg/storage/utils/decomposedfs/node/locks.go | 202 ++++++ .../utils/decomposedfs/node/locks_test.go | 218 +++++++ pkg/storage/utils/decomposedfs/node/node.go | 37 +- .../utils/decomposedfs/node/node_test.go | 22 + .../utils/decomposedfs/node/permissions.go | 24 +- pkg/storage/utils/decomposedfs/revisions.go | 5 + pkg/storage/utils/decomposedfs/tree/tree.go | 7 + .../utils/decomposedfs/tree/tree_test.go | 72 ++- pkg/storage/utils/decomposedfs/upload.go | 10 + pkg/storage/utils/eosfs/eosfs.go | 2 +- pkg/storage/utils/localfs/localfs.go | 2 +- pkg/utils/utils.go | 35 + .../expected-failures-on-OCIS-storage.md | 8 +- .../expected-failures-on-S3NG-storage.md | 8 +- .../integration/grpc/storageprovider_test.go | 111 ++++ 52 files changed, 2665 insertions(+), 702 deletions(-) create mode 100644 changelog/unreleased/decomposedfs-locking.md create mode 100644 internal/http/services/owncloud/ocdav/if.go create mode 100644 internal/http/services/owncloud/ocdav/if_test.go delete mode 100644 internal/http/services/owncloud/ocdav/lock.go create mode 100644 internal/http/services/owncloud/ocdav/locks.go rename internal/http/services/owncloud/ocdav/unlock.go => pkg/ctx/lockctx.go (67%) create mode 100644 pkg/storage/utils/decomposedfs/node/locks.go create mode 100644 pkg/storage/utils/decomposedfs/node/locks_test.go diff --git a/changelog/unreleased/decomposedfs-locking.md b/changelog/unreleased/decomposedfs-locking.md new file mode 100644 index 00000000000..3e027c95796 --- /dev/null +++ b/changelog/unreleased/decomposedfs-locking.md @@ -0,0 +1,5 @@ +Enhancement: add locking support to decomposedfs + +The decomposedfs now implements application level locking + +https://github.com/cs3org/reva/pull/2460 diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index b2337d64f1b..4c28d856d6e 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -743,6 +743,7 @@ func (s *svc) SetLock(ctx context.Context, req *provider.SetLockRequest) (*provi return nil, errors.Wrap(err, "gateway: error calling SetLock") } + s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), req.Ref.ResourceId) return res, nil } @@ -783,6 +784,7 @@ func (s *svc) RefreshLock(ctx context.Context, req *provider.RefreshLockRequest) return nil, errors.Wrap(err, "gateway: error calling RefreshLock") } + s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), req.Ref.ResourceId) return res, nil } @@ -803,6 +805,7 @@ func (s *svc) Unlock(ctx context.Context, req *provider.UnlockRequest) (*provide return nil, errors.Wrap(err, "gateway: error calling Unlock") } + s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), req.Ref.ResourceId) return res, nil } diff --git a/internal/grpc/services/gateway/usershareprovider.go b/internal/grpc/services/gateway/usershareprovider.go index c3e88d16d1a..2d40c80ffe5 100644 --- a/internal/grpc/services/gateway/usershareprovider.go +++ b/internal/grpc/services/gateway/usershareprovider.go @@ -66,26 +66,27 @@ func (s *svc) CreateShare(ctx context.Context, req *collaboration.CreateShareReq // TODO(labkode): if both commits are enabled they could be done concurrently. if s.c.CommitShareToStorageGrant { // If the share is a denial we call denyGrant instead. + var status *rpc.Status if grants.PermissionsEqual(req.Grant.Permissions.Permissions, &provider.ResourcePermissions{}) { - denyGrantStatus, err := s.denyGrant(ctx, req.ResourceInfo.Id, req.Grant.Grantee) + status, err = s.denyGrant(ctx, req.ResourceInfo.Id, req.Grant.Grantee) if err != nil { return nil, errors.Wrap(err, "gateway: error denying grant in storage") } - if denyGrantStatus.Code != rpc.Code_CODE_OK { - return &collaboration.CreateShareResponse{ - Status: denyGrantStatus, - }, err + } else { + status, err = s.addGrant(ctx, req.ResourceInfo.Id, req.Grant.Grantee, req.Grant.Permissions.Permissions) + if err != nil { + return nil, errors.Wrap(err, "gateway: error adding grant to storage") } - return res, nil } - addGrantStatus, err := s.addGrant(ctx, req.ResourceInfo.Id, req.Grant.Grantee, req.Grant.Permissions.Permissions) - if err != nil { - return nil, errors.Wrap(err, "gateway: error adding grant to storage") - } - if addGrantStatus.Code != rpc.Code_CODE_OK { + switch status.Code { + case rpc.Code_CODE_OK: + // ok + case rpc.Code_CODE_UNIMPLEMENTED: + appctx.GetLogger(ctx).Debug().Interface("status", status).Interface("req", req).Msg("storing grants not supported, ignoring") + default: return &collaboration.CreateShareResponse{ - Status: addGrantStatus, + Status: status, }, err } } @@ -493,12 +494,7 @@ func (s *svc) denyGrant(ctx context.Context, id *provider.ResourceId, g *provide if err != nil { return nil, errors.Wrap(err, "gateway: error calling DenyGrant") } - if grantRes.Status.Code != rpc.Code_CODE_OK { - return status.NewInternal(ctx, - "error committing share to storage grant"), nil - } - - return status.NewOK(ctx), nil + return grantRes.Status, nil } func (s *svc) addGrant(ctx context.Context, id *provider.ResourceId, g *provider.Grantee, p *provider.ResourcePermissions) (*rpc.Status, error) { @@ -530,12 +526,7 @@ func (s *svc) addGrant(ctx context.Context, id *provider.ResourceId, g *provider if err != nil { return nil, errors.Wrap(err, "gateway: error calling AddGrant") } - if grantRes.Status.Code != rpc.Code_CODE_OK { - return status.NewInternal(ctx, - "error committing share to storage grant"), nil - } - - return status.NewOK(ctx), nil + return grantRes.Status, nil } func (s *svc) updateGrant(ctx context.Context, id *provider.ResourceId, g *provider.Grantee, p *provider.ResourcePermissions) (*rpc.Status, error) { diff --git a/internal/grpc/services/storageprovider/storageprovider.go b/internal/grpc/services/storageprovider/storageprovider.go index 8219d2178e0..c7fd5d9a064 100644 --- a/internal/grpc/services/storageprovider/storageprovider.go +++ b/internal/grpc/services/storageprovider/storageprovider.go @@ -186,152 +186,70 @@ func registerMimeTypes(mimes map[string]string) { } func (s *service) SetArbitraryMetadata(ctx context.Context, req *provider.SetArbitraryMetadataRequest) (*provider.SetArbitraryMetadataResponse, error) { - if err := s.storage.SetArbitraryMetadata(ctx, req.Ref, req.ArbitraryMetadata); err != nil { - var st *rpc.Status - switch err.(type) { - case errtypes.IsNotFound: - st = status.NewNotFound(ctx, "path not found when setting arbitrary metadata") - case errtypes.PermissionDenied: - st = status.NewPermissionDenied(ctx, err, "permission denied") - default: - st = status.NewInternal(ctx, "error setting arbitrary metadata: "+req.Ref.String()) + // FIXME these should be part of the SetArbitraryMetadataRequest object + if req.Opaque != nil { + if e, ok := req.Opaque.Map["lockid"]; ok && e.Decoder == "plain" { + ctx = ctxpkg.ContextSetLockID(ctx, string(e.Value)) } - appctx.GetLogger(ctx). - Error(). - Err(err). - Interface("status", st). - Msg("failed to set arbitrary metadata") - return &provider.SetArbitraryMetadataResponse{ - Status: st, - }, nil } - res := &provider.SetArbitraryMetadataResponse{ - Status: status.NewOK(ctx), - } - return res, nil + err := s.storage.SetArbitraryMetadata(ctx, req.Ref, req.ArbitraryMetadata) + + return &provider.SetArbitraryMetadataResponse{ + Status: status.NewStatusFromErrType(ctx, "set arbitrary metadata", err), + }, nil } func (s *service) UnsetArbitraryMetadata(ctx context.Context, req *provider.UnsetArbitraryMetadataRequest) (*provider.UnsetArbitraryMetadataResponse, error) { - if err := s.storage.UnsetArbitraryMetadata(ctx, req.Ref, req.ArbitraryMetadataKeys); err != nil { - var st *rpc.Status - switch err.(type) { - case errtypes.IsNotFound: - st = status.NewNotFound(ctx, "path not found when unsetting arbitrary metadata") - case errtypes.PermissionDenied: - st = status.NewPermissionDenied(ctx, err, "permission denied") - default: - st = status.NewInternal(ctx, "error unsetting arbitrary metadata: "+req.Ref.String()) + // FIXME these should be part of the UnsetArbitraryMetadataRequest object + if req.Opaque != nil { + if e, ok := req.Opaque.Map["lockid"]; ok && e.Decoder == "plain" { + ctx = ctxpkg.ContextSetLockID(ctx, string(e.Value)) } - appctx.GetLogger(ctx). - Error(). - Err(err). - Interface("status", st). - Msg("failed to unset arbitrary metadata") - return &provider.UnsetArbitraryMetadataResponse{ - Status: st, - }, nil } - res := &provider.UnsetArbitraryMetadataResponse{ - Status: status.NewOK(ctx), - } - return res, nil + err := s.storage.UnsetArbitraryMetadata(ctx, req.Ref, req.ArbitraryMetadataKeys) + + return &provider.UnsetArbitraryMetadataResponse{ + Status: status.NewStatusFromErrType(ctx, "unset arbitrary metadata", err), + }, nil } // SetLock puts a lock on the given reference func (s *service) SetLock(ctx context.Context, req *provider.SetLockRequest) (*provider.SetLockResponse, error) { - if err := s.storage.SetLock(ctx, req.Ref, req.Lock); err != nil { - var st *rpc.Status - switch err.(type) { - case errtypes.IsNotFound: - st = status.NewNotFound(ctx, "path not found when setting lock") - case errtypes.PermissionDenied: - st = status.NewPermissionDenied(ctx, err, "permission denied") - default: - st = status.NewInternal(ctx, fmt.Sprintf("error setting lock %s: %s", req.Ref.String(), err)) - } - return &provider.SetLockResponse{ - Status: st, - }, nil - } + err := s.storage.SetLock(ctx, req.Ref, req.Lock) - res := &provider.SetLockResponse{ - Status: status.NewOK(ctx), - } - return res, nil + return &provider.SetLockResponse{ + Status: status.NewStatusFromErrType(ctx, "set lock", err), + }, nil } // GetLock returns an existing lock on the given reference func (s *service) GetLock(ctx context.Context, req *provider.GetLockRequest) (*provider.GetLockResponse, error) { - var lock *provider.Lock - var err error - if lock, err = s.storage.GetLock(ctx, req.Ref); err != nil { - var st *rpc.Status - switch err.(type) { - case errtypes.IsNotFound: - st = status.NewNotFound(ctx, "path not found when getting lock") - case errtypes.PermissionDenied: - st = status.NewPermissionDenied(ctx, err, "permission denied") - default: - st = status.NewInternal(ctx, fmt.Sprintf("error getting lock %s: %s", req.Ref.String(), err)) - } - return &provider.GetLockResponse{ - Status: st, - }, nil - } + lock, err := s.storage.GetLock(ctx, req.Ref) - res := &provider.GetLockResponse{ - Status: status.NewOK(ctx), + return &provider.GetLockResponse{ + Status: status.NewStatusFromErrType(ctx, "get lock", err), Lock: lock, - } - return res, nil + }, nil } // RefreshLock refreshes an existing lock on the given reference func (s *service) RefreshLock(ctx context.Context, req *provider.RefreshLockRequest) (*provider.RefreshLockResponse, error) { - if err := s.storage.RefreshLock(ctx, req.Ref, req.Lock); err != nil { - var st *rpc.Status - switch err.(type) { - case errtypes.IsNotFound: - st = status.NewNotFound(ctx, "path not found when refreshing lock") - case errtypes.PermissionDenied: - st = status.NewPermissionDenied(ctx, err, "permission denied") - default: - st = status.NewInternal(ctx, fmt.Sprintf("error refreshing lock %s: %s", req.Ref.String(), err)) - } - return &provider.RefreshLockResponse{ - Status: st, - }, nil - } + err := s.storage.RefreshLock(ctx, req.Ref, req.Lock) - res := &provider.RefreshLockResponse{ - Status: status.NewOK(ctx), - } - return res, nil + return &provider.RefreshLockResponse{ + Status: status.NewStatusFromErrType(ctx, "refresh lock", err), + }, nil } // Unlock removes an existing lock from the given reference func (s *service) Unlock(ctx context.Context, req *provider.UnlockRequest) (*provider.UnlockResponse, error) { - if err := s.storage.Unlock(ctx, req.Ref); err != nil { - var st *rpc.Status - switch err.(type) { - case errtypes.IsNotFound: - st = status.NewNotFound(ctx, "path not found when unlocking") - case errtypes.PermissionDenied: - st = status.NewPermissionDenied(ctx, err, "permission denied") - default: - st = status.NewInternal(ctx, fmt.Sprintf("error unlocking %s: %s", req.Ref.String(), err)) - } - return &provider.UnlockResponse{ - Status: st, - }, nil - } + err := s.storage.Unlock(ctx, req.Ref, req.Lock) - res := &provider.UnlockResponse{ - Status: status.NewOK(ctx), - } - return res, nil + return &provider.UnlockResponse{ + Status: status.NewStatusFromErrType(ctx, "unlock", err), + }, nil } func (s *service) InitiateFileDownload(ctx context.Context, req *provider.InitiateFileDownloadRequest) (*provider.InitiateFileDownloadResponse, error) { @@ -373,6 +291,13 @@ func (s *service) InitiateFileUpload(ctx context.Context, req *provider.Initiate }, nil } + // FIXME these should be part of the InitiateFileUploadRequest object + if req.Opaque != nil { + if e, ok := req.Opaque.Map["lockid"]; ok && e.Decoder == "plain" { + ctx = ctxpkg.ContextSetLockID(ctx, string(e.Value)) + } + } + metadata := map[string]string{} var uploadLength int64 if req.Opaque != nil && req.Opaque.Map != nil { @@ -603,57 +528,33 @@ func (s *service) DeleteStorageSpace(ctx context.Context, req *provider.DeleteSt } func (s *service) CreateContainer(ctx context.Context, req *provider.CreateContainerRequest) (*provider.CreateContainerResponse, error) { - if err := s.storage.CreateDir(ctx, req.Ref); err != nil { - var st *rpc.Status - switch err.(type) { - case errtypes.IsNotFound: - st = status.NewNotFound(ctx, "path not found when creating container") - case errtypes.AlreadyExists: - st = status.NewAlreadyExists(ctx, err, "container already exists") - case errtypes.PermissionDenied: - st = status.NewPermissionDenied(ctx, err, "permission denied") - default: - st = status.NewInternal(ctx, "error creating container: "+req.Ref.String()) + // FIXME these should be part of the CreateContainerRequest object + if req.Opaque != nil { + if e, ok := req.Opaque.Map["lockid"]; ok && e.Decoder == "plain" { + ctx = ctxpkg.ContextSetLockID(ctx, string(e.Value)) } - appctx.GetLogger(ctx). - Error(). - Err(err). - Interface("status", st). - Interface("reference", req.Ref). - Msg("failed to create container") - return &provider.CreateContainerResponse{ - Status: st, - }, nil } - res := &provider.CreateContainerResponse{ - Status: status.NewOK(ctx), - } - return res, nil + err := s.storage.CreateDir(ctx, req.Ref) + + return &provider.CreateContainerResponse{ + Status: status.NewStatusFromErrType(ctx, "create container", err), + }, nil } func (s *service) TouchFile(ctx context.Context, req *provider.TouchFileRequest) (*provider.TouchFileResponse, error) { - if err := s.storage.TouchFile(ctx, req.Ref); err != nil { - var st *rpc.Status - switch err.(type) { - case errtypes.IsNotFound: - st = status.NewNotFound(ctx, "path not found when touching the file") - case errtypes.AlreadyExists: - st = status.NewAlreadyExists(ctx, err, "file already exists") - case errtypes.PermissionDenied: - st = status.NewPermissionDenied(ctx, err, "permission denied") - default: - st = status.NewInternal(ctx, "error touching file: "+req.Ref.String()) + // FIXME these should be part of the TouchFileRequest object + if req.Opaque != nil { + if e, ok := req.Opaque.Map["lockid"]; ok && e.Decoder == "plain" { + ctx = ctxpkg.ContextSetLockID(ctx, string(e.Value)) } - return &provider.TouchFileResponse{ - Status: st, - }, nil } - res := &provider.TouchFileResponse{ - Status: status.NewOK(ctx), - } - return res, nil + err := s.storage.TouchFile(ctx, req.Ref) + + return &provider.TouchFileResponse{ + Status: status.NewStatusFromErrType(ctx, "touch file", err), + }, nil } func (s *service) Delete(ctx context.Context, req *provider.DeleteRequest) (*provider.DeleteResponse, error) { @@ -664,68 +565,36 @@ func (s *service) Delete(ctx context.Context, req *provider.DeleteRequest) (*pro } // check DeleteRequest for any known opaque properties. + // FIXME these should be part of the DeleteRequest object if req.Opaque != nil { - _, ok := req.Opaque.Map["deleting_shared_resource"] - if ok { + if _, ok := req.Opaque.Map["deleting_shared_resource"]; ok { // it is a binary key; its existence signals true. Although, do not assume. ctx = context.WithValue(ctx, appctx.DeletingSharedResource, true) } - } - - if err := s.storage.Delete(ctx, req.Ref); err != nil { - var st *rpc.Status - switch err.(type) { - case errtypes.IsNotFound: - st = status.NewNotFound(ctx, "path not found when creating container") - case errtypes.PermissionDenied: - st = status.NewPermissionDenied(ctx, err, "permission denied") - default: - st = status.NewInternal(ctx, "error deleting file: "+req.Ref.String()) + if e, ok := req.Opaque.Map["lockid"]; ok && e.Decoder == "plain" { + ctx = ctxpkg.ContextSetLockID(ctx, string(e.Value)) } - appctx.GetLogger(ctx). - Error(). - Err(err). - Interface("status", st). - Interface("reference", req.Ref). - Msg("failed to delete") - return &provider.DeleteResponse{ - Status: st, - }, nil } - res := &provider.DeleteResponse{ - Status: status.NewOK(ctx), - } - return res, nil + err := s.storage.Delete(ctx, req.Ref) + + return &provider.DeleteResponse{ + Status: status.NewStatusFromErrType(ctx, "delete", err), + }, nil } func (s *service) Move(ctx context.Context, req *provider.MoveRequest) (*provider.MoveResponse, error) { - if err := s.storage.Move(ctx, req.Source, req.Destination); err != nil { - var st *rpc.Status - switch err.(type) { - case errtypes.IsNotFound: - st = status.NewNotFound(ctx, "path not found when moving") - case errtypes.PermissionDenied: - st = status.NewPermissionDenied(ctx, err, "permission denied") - default: - st = status.NewInternal(ctx, "error moving: "+req.Source.String()) + // FIXME these should be part of the MoveRequest object + if req.Opaque != nil { + if e, ok := req.Opaque.Map["lockid"]; ok && e.Decoder == "plain" { + ctx = ctxpkg.ContextSetLockID(ctx, string(e.Value)) } - appctx.GetLogger(ctx). - Error(). - Err(err). - Interface("status", st). - Interface("source_reference", req.Source). - Interface("target_reference", req.Destination). - Msg("failed to move") - return &provider.MoveResponse{ - Status: st, - }, nil } + err := s.storage.Move(ctx, req.Source, req.Destination) - res := &provider.MoveResponse{ - Status: status.NewOK(ctx), - } - return res, nil + return &provider.MoveResponse{ + Status: status.NewStatusFromErrType(ctx, "move", err), + }, nil } func (s *service) Stat(ctx context.Context, req *provider.StatRequest) (*provider.StatResponse, error) { @@ -738,32 +607,11 @@ func (s *service) Stat(ctx context.Context, req *provider.StatRequest) (*provide }) md, err := s.storage.GetMD(ctx, req.Ref, req.ArbitraryMetadataKeys) - if err != nil { - var st *rpc.Status - switch err.(type) { - case errtypes.IsNotFound: - st = status.NewNotFound(ctx, "path not found when statting") - case errtypes.PermissionDenied: - st = status.NewPermissionDenied(ctx, err, "permission denied") - default: - st = status.NewInternal(ctx, "error statting: "+req.Ref.String()) - } - appctx.GetLogger(ctx). - Error(). - Err(err). - Interface("status", st). - Interface("reference", req.Ref). - Msg("failed to stat") - return &provider.StatResponse{ - Status: st, - }, nil - } - res := &provider.StatResponse{ - Status: status.NewOK(ctx), + return &provider.StatResponse{ + Status: status.NewStatusFromErrType(ctx, "stat", err), Info: md, - } - return res, nil + }, nil } func (s *service) ListContainerStream(req *provider.ListContainerStreamRequest, ss provider.ProviderAPI_ListContainerStreamServer) error { @@ -812,29 +660,9 @@ func (s *service) ListContainerStream(req *provider.ListContainerStreamRequest, func (s *service) ListContainer(ctx context.Context, req *provider.ListContainerRequest) (*provider.ListContainerResponse, error) { mds, err := s.storage.ListFolder(ctx, req.Ref, req.ArbitraryMetadataKeys) - if err != nil { - var st *rpc.Status - switch err.(type) { - case errtypes.IsNotFound: - st = status.NewNotFound(ctx, "path not found when listing container") - case errtypes.PermissionDenied: - st = status.NewPermissionDenied(ctx, err, "permission denied") - default: - st = status.NewInternal(ctx, "error listing container: "+req.Ref.String()) - } - appctx.GetLogger(ctx). - Error(). - Err(err). - Interface("status", st). - Interface("reference", req.Ref). - Msg("failed to list folder") - return &provider.ListContainerResponse{ - Status: st, - }, nil - } res := &provider.ListContainerResponse{ - Status: status.NewOK(ctx), + Status: status.NewStatusFromErrType(ctx, "list container", err), Infos: mds, } return res, nil @@ -842,63 +670,27 @@ func (s *service) ListContainer(ctx context.Context, req *provider.ListContainer func (s *service) ListFileVersions(ctx context.Context, req *provider.ListFileVersionsRequest) (*provider.ListFileVersionsResponse, error) { revs, err := s.storage.ListRevisions(ctx, req.Ref) - if err != nil { - var st *rpc.Status - switch err.(type) { - case errtypes.IsNotFound: - st = status.NewNotFound(ctx, "path not found when listing file versions") - case errtypes.PermissionDenied: - st = status.NewPermissionDenied(ctx, err, "permission denied") - default: - st = status.NewInternal(ctx, "error listing file versions: "+req.Ref.String()) - } - appctx.GetLogger(ctx). - Error(). - Err(err). - Interface("status", st). - Interface("reference", req.Ref). - Msg("failed to list file versions") - return &provider.ListFileVersionsResponse{ - Status: st, - }, nil - } sort.Sort(descendingMtime(revs)) - res := &provider.ListFileVersionsResponse{ - Status: status.NewOK(ctx), + return &provider.ListFileVersionsResponse{ + Status: status.NewStatusFromErrType(ctx, "list file versions", err), Versions: revs, - } - return res, nil + }, nil } func (s *service) RestoreFileVersion(ctx context.Context, req *provider.RestoreFileVersionRequest) (*provider.RestoreFileVersionResponse, error) { - if err := s.storage.RestoreRevision(ctx, req.Ref, req.Key); err != nil { - var st *rpc.Status - switch err.(type) { - case errtypes.IsNotFound: - st = status.NewNotFound(ctx, "path not found when restoring file versions") - case errtypes.PermissionDenied: - st = status.NewPermissionDenied(ctx, err, "permission denied") - default: - st = status.NewInternal(ctx, "error restoring version: "+req.Ref.String()) + // FIXME these should be part of the RestoreFileVersionRequest object + if req.Opaque != nil { + if e, ok := req.Opaque.Map["lockid"]; ok && e.Decoder == "plain" { + ctx = ctxpkg.ContextSetLockID(ctx, string(e.Value)) } - appctx.GetLogger(ctx). - Error(). - Err(err). - Interface("status", st). - Interface("reference", req.Ref). - Str("key", req.Key). - Msg("failed to restore file version") - return &provider.RestoreFileVersionResponse{ - Status: st, - }, nil } + err := s.storage.RestoreRevision(ctx, req.Ref, req.Key) - res := &provider.RestoreFileVersionResponse{ - Status: status.NewOK(ctx), - } - return res, nil + return &provider.RestoreFileVersionResponse{ + Status: status.NewStatusFromErrType(ctx, "restore file version", err), + }, nil } func (s *service) ListRecycleStream(req *provider.ListRecycleStreamRequest, ss provider.ProviderAPI_ListRecycleStreamServer) error { @@ -980,37 +772,31 @@ func (s *service) ListRecycle(ctx context.Context, req *provider.ListRecycleRequ } func (s *service) RestoreRecycleItem(ctx context.Context, req *provider.RestoreRecycleItemRequest) (*provider.RestoreRecycleItemResponse, error) { - // TODO(labkode): CRITICAL: fill recycle info with storage provider. - key, itemPath := router.ShiftPath(req.Key) - if err := s.storage.RestoreRecycleItem(ctx, req.Ref, key, itemPath, req.RestoreRef); err != nil { - var st *rpc.Status - switch err.(type) { - case errtypes.IsNotFound: - st = status.NewNotFound(ctx, "path not found when restoring recycle bin item") - case errtypes.PermissionDenied: - st = status.NewPermissionDenied(ctx, err, "permission denied") - default: - st = status.NewInternal(ctx, "error restoring recycle bin item") + // FIXME these should be part of the RestoreRecycleItemRequest object + if req.Opaque != nil { + if e, ok := req.Opaque.Map["lockid"]; ok && e.Decoder == "plain" { + ctx = ctxpkg.ContextSetLockID(ctx, string(e.Value)) } - appctx.GetLogger(ctx). - Error(). - Err(err). - Interface("status", st). - Interface("reference", req.Ref). - Str("key", req.Key). - Msg("failed to restore recycle item") - return &provider.RestoreRecycleItemResponse{ - Status: st, - }, nil } + // TODO(labkode): CRITICAL: fill recycle info with storage provider. + key, itemPath := router.ShiftPath(req.Key) + err := s.storage.RestoreRecycleItem(ctx, req.Ref, key, itemPath, req.RestoreRef) + res := &provider.RestoreRecycleItemResponse{ - Status: status.NewOK(ctx), + Status: status.NewStatusFromErrType(ctx, "restore recycle item", err), } return res, nil } func (s *service) PurgeRecycle(ctx context.Context, req *provider.PurgeRecycleRequest) (*provider.PurgeRecycleResponse, error) { + // FIXME these should be part of the PurgeRecycleRequest object + if req.Opaque != nil { + if e, ok := req.Opaque.Map["lockid"]; ok && e.Decoder == "plain" { + ctx = ctxpkg.ContextSetLockID(ctx, string(e.Value)) + } + } + // if a key was sent as opaque id purge only that item key, itemPath := router.ShiftPath(req.Key) if key != "" { @@ -1121,12 +907,15 @@ func (s *service) DenyGrant(ctx context.Context, req *provider.DenyGrantRequest) func (s *service) AddGrant(ctx context.Context, req *provider.AddGrantRequest) (*provider.AddGrantResponse, error) { // TODO: update CS3 APIs + // FIXME these should be part of the AddGrantRequest object if req.Opaque != nil { _, spacegrant := req.Opaque.Map["spacegrant"] if spacegrant { ctx = context.WithValue(ctx, utils.SpaceGrant, struct{}{}) } - + if e, ok := req.Opaque.Map["lockid"]; ok && e.Decoder == "plain" { + ctx = ctxpkg.ContextSetLockID(ctx, string(e.Value)) + } } // check grantee type is valid @@ -1137,39 +926,20 @@ func (s *service) AddGrant(ctx context.Context, req *provider.AddGrantRequest) ( } err := s.storage.AddGrant(ctx, req.Ref, req.Grant) - if err != nil { - var st *rpc.Status - switch err.(type) { - case errtypes.NotSupported: - // ignore - setting storage grants is optional - return &provider.AddGrantResponse{ - Status: status.NewOK(ctx), - }, nil - case errtypes.IsNotFound: - st = status.NewNotFound(ctx, "path not found when setting grants") - case errtypes.PermissionDenied: - st = status.NewPermissionDenied(ctx, err, "permission denied") - default: - st = status.NewInternal(ctx, "error setting grants") - } - appctx.GetLogger(ctx). - Error(). - Err(err). - Interface("status", st). - Interface("reference", req.Ref). - Msg("failed to add grant") - return &provider.AddGrantResponse{ - Status: st, - }, nil - } - res := &provider.AddGrantResponse{ - Status: status.NewOK(ctx), - } - return res, nil + return &provider.AddGrantResponse{ + Status: status.NewStatusFromErrType(ctx, "add grant", err), + }, nil } func (s *service) UpdateGrant(ctx context.Context, req *provider.UpdateGrantRequest) (*provider.UpdateGrantResponse, error) { + // FIXME these should be part of the UpdateGrantRequest object + if req.Opaque != nil { + if e, ok := req.Opaque.Map["lockid"]; ok && e.Decoder == "plain" { + ctx = ctxpkg.ContextSetLockID(ctx, string(e.Value)) + } + } + // check grantee type is valid if req.Grant.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_INVALID { return &provider.UpdateGrantResponse{ @@ -1177,39 +947,21 @@ func (s *service) UpdateGrant(ctx context.Context, req *provider.UpdateGrantRequ }, nil } - if err := s.storage.UpdateGrant(ctx, req.Ref, req.Grant); err != nil { - var st *rpc.Status - switch err.(type) { - case errtypes.NotSupported: - // ignore - setting storage grants is optional - return &provider.UpdateGrantResponse{ - Status: status.NewOK(ctx), - }, nil - case errtypes.IsNotFound: - st = status.NewNotFound(ctx, "path not found when updating grant") - case errtypes.PermissionDenied: - st = status.NewPermissionDenied(ctx, err, "permission denied") - default: - st = status.NewInternal(ctx, "error updating grant") - } - appctx.GetLogger(ctx). - Error(). - Err(err). - Interface("status", st). - Interface("reference", req.Ref). - Msg("failed to update grant") - return &provider.UpdateGrantResponse{ - Status: st, - }, nil - } + err := s.storage.UpdateGrant(ctx, req.Ref, req.Grant) - res := &provider.UpdateGrantResponse{ - Status: status.NewOK(ctx), - } - return res, nil + return &provider.UpdateGrantResponse{ + Status: status.NewStatusFromErrType(ctx, "update grant", err), + }, nil } func (s *service) RemoveGrant(ctx context.Context, req *provider.RemoveGrantRequest) (*provider.RemoveGrantResponse, error) { + // FIXME these should be part of the RemoveGrantRequest object + if req.Opaque != nil { + if e, ok := req.Opaque.Map["lockid"]; ok && e.Decoder == "plain" { + ctx = ctxpkg.ContextSetLockID(ctx, string(e.Value)) + } + } + // check targetType is valid if req.Grant.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_INVALID { return &provider.RemoveGrantResponse{ @@ -1217,31 +969,11 @@ func (s *service) RemoveGrant(ctx context.Context, req *provider.RemoveGrantRequ }, nil } - if err := s.storage.RemoveGrant(ctx, req.Ref, req.Grant); err != nil { - var st *rpc.Status - switch err.(type) { - case errtypes.IsNotFound: - st = status.NewNotFound(ctx, "path not found when removing grant") - case errtypes.PermissionDenied: - st = status.NewPermissionDenied(ctx, err, "permission denied") - default: - st = status.NewInternal(ctx, "error removing grant") - } - appctx.GetLogger(ctx). - Error(). - Err(err). - Interface("status", st). - Interface("reference", req.Ref). - Msg("failed to remove grant") - return &provider.RemoveGrantResponse{ - Status: st, - }, nil - } + err := s.storage.RemoveGrant(ctx, req.Ref, req.Grant) - res := &provider.RemoveGrantResponse{ - Status: status.NewOK(ctx), - } - return res, nil + return &provider.RemoveGrantResponse{ + Status: status.NewStatusFromErrType(ctx, "remove grant", err), + }, nil } func (s *service) CreateReference(ctx context.Context, req *provider.CreateReferenceRequest) (*provider.CreateReferenceResponse, error) { diff --git a/internal/http/services/owncloud/ocdav/copy.go b/internal/http/services/owncloud/ocdav/copy.go index 843a467f84f..53889dc8d49 100644 --- a/internal/http/services/owncloud/ocdav/copy.go +++ b/internal/http/services/owncloud/ocdav/copy.go @@ -128,7 +128,7 @@ func (s *svc) executePathCopy(ctx context.Context, client gateway.GatewayAPIClie if createRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { w.WriteHeader(http.StatusForbidden) m := fmt.Sprintf("Permission denied to create %v", createReq.Ref.Path) - b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + b, err := errors.Marshal(http.StatusForbidden, m, "") errors.HandleWebdavError(log, w, b, err) } return nil @@ -218,7 +218,7 @@ func (s *svc) executePathCopy(ctx context.Context, client gateway.GatewayAPIClie if uRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { w.WriteHeader(http.StatusForbidden) m := fmt.Sprintf("Permissions denied to create %v", uReq.Ref.Path) - b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + b, err := errors.Marshal(http.StatusForbidden, m, "") errors.HandleWebdavError(log, w, b, err) return nil } @@ -350,7 +350,7 @@ func (s *svc) executeSpacesCopy(ctx context.Context, w http.ResponseWriter, clie w.WriteHeader(http.StatusForbidden) // TODO path could be empty or relative... m := fmt.Sprintf("Permission denied to create %v", createReq.Ref.Path) - b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + b, err := errors.Marshal(http.StatusForbidden, m, "") errors.HandleWebdavError(log, w, b, err) } return nil @@ -426,7 +426,7 @@ func (s *svc) executeSpacesCopy(ctx context.Context, w http.ResponseWriter, clie w.WriteHeader(http.StatusForbidden) // TODO path can be empty or relative m := fmt.Sprintf("Permissions denied to create %v", uReq.Ref.Path) - b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + b, err := errors.Marshal(http.StatusForbidden, m, "") errors.HandleWebdavError(log, w, b, err) return nil } @@ -484,7 +484,7 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re if err != nil { w.WriteHeader(http.StatusBadRequest) m := fmt.Sprintf("Overwrite header is set to incorrect value %v", overwrite) - b, err := errors.Marshal(errors.SabredavBadRequest, m, "") + b, err := errors.Marshal(http.StatusBadRequest, m, "") errors.HandleWebdavError(log, w, b, err) return nil } @@ -494,7 +494,7 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re if err != nil { w.WriteHeader(http.StatusBadRequest) m := fmt.Sprintf("Depth header is set to incorrect value %v", dh) - b, err := errors.Marshal(errors.SabredavBadRequest, m, "") + b, err := errors.Marshal(http.StatusBadRequest, m, "") errors.HandleWebdavError(log, w, b, err) return nil } @@ -525,7 +525,7 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re if srcStatRes.Status.Code == rpc.Code_CODE_NOT_FOUND { w.WriteHeader(http.StatusNotFound) m := fmt.Sprintf("Resource %v not found", srcStatReq.Ref.Path) - b, err := errors.Marshal(errors.SabredavNotFound, m, "") + b, err := errors.Marshal(http.StatusNotFound, m, "") errors.HandleWebdavError(log, w, b, err) } errors.HandleErrorStatus(log, w, srcStatRes.Status) @@ -552,7 +552,7 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re log.Warn().Str("overwrite", overwrite).Msg("dst already exists") w.WriteHeader(http.StatusPreconditionFailed) m := fmt.Sprintf("Could not overwrite Resource %v", dstRef.Path) - b, err := errors.Marshal(errors.SabredavPreconditionFailed, m, "") + b, err := errors.Marshal(http.StatusPreconditionFailed, m, "") errors.HandleWebdavError(log, w, b, err) // 412, see https://tools.ietf.org/html/rfc4918#section-9.8.5 return nil } diff --git a/internal/http/services/owncloud/ocdav/dav.go b/internal/http/services/owncloud/ocdav/dav.go index def7ab68c5f..84f757ddcb4 100644 --- a/internal/http/services/owncloud/ocdav/dav.go +++ b/internal/http/services/owncloud/ocdav/dav.go @@ -110,9 +110,9 @@ func (h *DavHandler) Handler(s *svc) http.Handler { r.URL.Path = path.Join(r.URL.Path, contextUser.Username) } - if r.Header.Get("Depth") == "" { + if r.Header.Get(net.HeaderDepth) == "" { w.WriteHeader(http.StatusMethodNotAllowed) - b, err := errors.Marshal(errors.SabredavMethodNotAllowed, "Listing members of this collection is disabled", "") + b, err := errors.Marshal(http.StatusMethodNotAllowed, "Listing members of this collection is disabled", "") if err != nil { log.Error().Msgf("error marshaling xml response: %s", b) w.WriteHeader(http.StatusInternalServerError) @@ -208,11 +208,11 @@ func (h *DavHandler) Handler(s *svc) http.Handler { case res.Status.Code == rpcv1beta1.Code_CODE_UNAUTHENTICATED: w.WriteHeader(http.StatusUnauthorized) if hasValidBasicAuthHeader { - b, err := errors.Marshal(errors.SabredavNotAuthenticated, "Username or password was incorrect", "") + b, err := errors.Marshal(http.StatusUnauthorized, "Username or password was incorrect", "") errors.HandleWebdavError(log, w, b, err) return } - b, err := errors.Marshal(errors.SabredavNotAuthenticated, "No 'Authorization: Basic' header found", "") + b, err := errors.Marshal(http.StatusUnauthorized, "No 'Authorization: Basic' header found", "") errors.HandleWebdavError(log, w, b, err) return case res.Status.Code == rpcv1beta1.Code_CODE_NOT_FOUND: @@ -263,7 +263,7 @@ func (h *DavHandler) Handler(s *svc) http.Handler { default: w.WriteHeader(http.StatusNotFound) - b, err := errors.Marshal(errors.SabredavNotFound, "File not found in root", "") + b, err := errors.Marshal(http.StatusNotFound, "File not found in root", "") errors.HandleWebdavError(log, w, b, err) } }) diff --git a/internal/http/services/owncloud/ocdav/delete.go b/internal/http/services/owncloud/ocdav/delete.go index c8d74248271..4df9b67bb67 100644 --- a/internal/http/services/owncloud/ocdav/delete.go +++ b/internal/http/services/owncloud/ocdav/delete.go @@ -27,9 +27,12 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/rgrpc/status" rtrace "github.com/cs3org/reva/pkg/trace" + "github.com/cs3org/reva/pkg/utils" "github.com/rs/zerolog" ) @@ -58,49 +61,72 @@ func (s *svc) handlePathDelete(w http.ResponseWriter, r *http.Request, ns string } func (s *svc) handleDelete(ctx context.Context, w http.ResponseWriter, r *http.Request, ref *provider.Reference, log zerolog.Logger) { + + ctx, span := rtrace.Provider.Tracer("reva").Start(ctx, "delete") + defer span.End() + + req := &provider.DeleteRequest{Ref: ref} + + // FIXME the lock token is part of the application level protocol, it should be part of the DeleteRequest message not the opaque + ih, ok := parseIfHeader(r.Header.Get(net.HeaderIf)) + if ok { + if len(ih.lists) == 1 && len(ih.lists[0].conditions) == 1 { + req.Opaque = utils.AppendPlainToOpaque(req.Opaque, "lockid", ih.lists[0].conditions[0].Token) + } + } else if r.Header.Get(net.HeaderIf) != "" { + w.WriteHeader(http.StatusBadRequest) + b, err := errors.Marshal(http.StatusBadRequest, "invalid if header", "") + errors.HandleWebdavError(&log, w, b, err) + return + } + client, err := s.getClient() if err != nil { log.Error().Err(err).Msg("error getting grpc client") w.WriteHeader(http.StatusInternalServerError) return } - - ctx, span := rtrace.Provider.Tracer("reva").Start(ctx, "delete") - defer span.End() - - req := &provider.DeleteRequest{Ref: ref} res, err := client.Delete(ctx, req) if err != nil { span.RecordError(err) log.Error().Err(err).Msg("error performing delete grpc request") w.WriteHeader(http.StatusInternalServerError) return - } else if res.Status.Code != rpc.Code_CODE_OK { - if res.Status.Code == rpc.Code_CODE_NOT_FOUND { - w.WriteHeader(http.StatusNotFound) - // TODO path might be empty or relative... - m := fmt.Sprintf("Resource %v not found", ref.Path) - b, err := errors.Marshal(errors.SabredavNotFound, m, "") - errors.HandleWebdavError(&log, w, b, err) - } - if res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { - w.WriteHeader(http.StatusForbidden) - // TODO path might be empty or relative... - m := fmt.Sprintf("Permission denied to delete %v", ref.Path) - b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") - errors.HandleWebdavError(&log, w, b, err) + } + switch res.Status.Code { + case rpc.Code_CODE_OK: + w.WriteHeader(http.StatusNoContent) + case rpc.Code_CODE_NOT_FOUND: + w.WriteHeader(http.StatusNotFound) + // TODO path might be empty or relative... + m := fmt.Sprintf("Resource %v not found", ref.Path) + b, err := errors.Marshal(http.StatusNotFound, m, "") + errors.HandleWebdavError(&log, w, b, err) + case rpc.Code_CODE_PERMISSION_DENIED: + status := http.StatusForbidden + if lockID := utils.ReadPlainFromOpaque(res.Opaque, "lockid"); lockID != "" { + // http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the + // Lock-Token value is a Coded-URL. We add angle brackets. + w.Header().Set("Lock-Token", "<"+lockID+">") + status = http.StatusLocked } - if res.Status.Code == rpc.Code_CODE_INTERNAL && res.Status.Message == "can't delete mount path" { + w.WriteHeader(status) + // TODO path might be empty or relative... + m := fmt.Sprintf("Permission denied to delete %v", ref.Path) + b, err := errors.Marshal(status, m, "") + errors.HandleWebdavError(&log, w, b, err) + case rpc.Code_CODE_INTERNAL: + if res.Status.Message == "can't delete mount path" { w.WriteHeader(http.StatusForbidden) - b, err := errors.Marshal(errors.SabredavPermissionDenied, res.Status.Message, "") + b, err := errors.Marshal(http.StatusForbidden, res.Status.Message, "") errors.HandleWebdavError(&log, w, b, err) } - - errors.HandleErrorStatus(&log, w, res.Status) - return + default: + status := status.HTTPStatusFromCode(res.Status.Code) + w.WriteHeader(status) + b, err := errors.Marshal(status, res.Status.Message, "") + errors.HandleWebdavError(&log, w, b, err) } - - w.WriteHeader(http.StatusNoContent) } func (s *svc) handleSpacesDelete(w http.ResponseWriter, r *http.Request, spaceID string) { diff --git a/internal/http/services/owncloud/ocdav/errors/error.go b/internal/http/services/owncloud/ocdav/errors/error.go index b7fde0d3453..b2dd46c0d44 100644 --- a/internal/http/services/owncloud/ocdav/errors/error.go +++ b/internal/http/services/owncloud/ocdav/errors/error.go @@ -24,55 +24,87 @@ import ( "net/http" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/pkg/errors" "github.com/rs/zerolog" ) -type code int +var sabreException = map[int]string{ -const ( + // the commented states have no corresponding exception in sabre/dav, + // see https://github.com/sabre-io/dav/tree/master/lib/DAV/Exception - // SabredavBadRequest maps to HTTP 400 - SabredavBadRequest code = iota - // SabredavMethodNotAllowed maps to HTTP 405 - SabredavMethodNotAllowed - // SabredavNotAuthenticated maps to HTTP 401 - SabredavNotAuthenticated - // SabredavPreconditionFailed maps to HTTP 412 - SabredavPreconditionFailed - // SabredavPermissionDenied maps to HTTP 403 - SabredavPermissionDenied - // SabredavNotFound maps to HTTP 404 - SabredavNotFound - // SabredavConflict maps to HTTP 409 - SabredavConflict -) + // http.StatusMultipleChoices: "Multiple Choices", + // http.StatusMovedPermanently: "Moved Permanently", + // http.StatusFound: "Found", + // http.StatusSeeOther: "See Other", + // http.StatusNotModified: "Not Modified", + // http.StatusUseProxy: "Use Proxy", + // http.StatusTemporaryRedirect: "Temporary Redirect", + // http.StatusPermanentRedirect: "Permanent Redirect", -var ( - codesEnum = []string{ - "Sabre\\DAV\\Exception\\BadRequest", - "Sabre\\DAV\\Exception\\MethodNotAllowed", - "Sabre\\DAV\\Exception\\NotAuthenticated", - "Sabre\\DAV\\Exception\\PreconditionFailed", - "Sabre\\DAV\\Exception\\PermissionDenied", - "Sabre\\DAV\\Exception\\NotFound", - "Sabre\\DAV\\Exception\\Conflict", - } -) + http.StatusBadRequest: "Sabre\\DAV\\Exception\\BadRequest", + http.StatusUnauthorized: "Sabre\\DAV\\Exception\\NotAuthenticated", + http.StatusPaymentRequired: "Sabre\\DAV\\Exception\\PaymentRequired", + http.StatusForbidden: "Sabre\\DAV\\Exception\\Forbidden", // InvalidResourceType, InvalidSyncToken, TooManyMatches + http.StatusNotFound: "Sabre\\DAV\\Exception\\NotFound", + http.StatusMethodNotAllowed: "Sabre\\DAV\\Exception\\MethodNotAllowed", + // http.StatusNotAcceptable: "Not Acceptable", + // http.StatusProxyAuthRequired: "Proxy Authentication Required", + // http.StatusRequestTimeout: "Request Timeout", + http.StatusConflict: "Sabre\\DAV\\Exception\\Conflict", // LockTokenMatchesRequestUri + // http.StatusGone: "Gone", + http.StatusLengthRequired: "Sabre\\DAV\\Exception\\LengthRequired", + http.StatusPreconditionFailed: "Sabre\\DAV\\Exception\\PreconditionFailed", + // http.StatusRequestEntityTooLarge: "Request Entity Too Large", + // http.StatusRequestURITooLong: "Request URI Too Long", + http.StatusUnsupportedMediaType: "Sabre\\DAV\\Exception\\UnsupportedMediaType", // ReportNotSupported + http.StatusRequestedRangeNotSatisfiable: "Sabre\\DAV\\Exception\\RequestedRangeNotSatisfiable", + // http.StatusExpectationFailed: "Expectation Failed", + // http.StatusTeapot: "I'm a teapot", + // http.StatusMisdirectedRequest: "Misdirected Request", + // http.StatusUnprocessableEntity: "Unprocessable Entity", + http.StatusLocked: "Sabre\\DAV\\Exception\\Locked", // ConflictingLock + // http.StatusFailedDependency: "Failed Dependency", + // http.StatusTooEarly: "Too Early", + // http.StatusUpgradeRequired: "Upgrade Required", + // http.StatusPreconditionRequired: "Precondition Required", + // http.StatusTooManyRequests: "Too Many Requests", + // http.StatusRequestHeaderFieldsTooLarge: "Request Header Fields Too Large", + // http.StatusUnavailableForLegalReasons: "Unavailable For Legal Reasons", + + // http.StatusInternalServerError: "Internal Server Error", + http.StatusNotImplemented: "Sabre\\DAV\\Exception\\NotImplemented", + // http.StatusBadGateway: "Bad Gateway", + http.StatusServiceUnavailable: "Sabre\\DAV\\Exception\\ServiceUnavailable", + // http.StatusGatewayTimeout: "Gateway Timeout", + // http.StatusHTTPVersionNotSupported: "HTTP Version Not Supported", + // http.StatusVariantAlsoNegotiates: "Variant Also Negotiates", + http.StatusInsufficientStorage: "Sabre\\DAV\\Exception\\InsufficientStorage", + // http.StatusLoopDetected: "Loop Detected", + // http.StatusNotExtended: "Not Extended", + // http.StatusNetworkAuthenticationRequired: "Network Authentication Required", +} + +// SabreException returns a sabre exception text for the HTTP status code. It returns the empty +// string if the code is unknown. +func SabreException(code int) string { + return sabreException[code] +} // Exception represents a ocdav exception type Exception struct { - Code code + Code int Message string Header string } // Marshal just calls the xml marshaller for a given exception. -func Marshal(code code, message string, header string) ([]byte, error) { +func Marshal(code int, message string, header string) ([]byte, error) { xmlstring, err := xml.Marshal(&ErrorXML{ Xmlnsd: "DAV", Xmlnss: "http://sabredav.org/ns", - Exception: codesEnum[code], + Exception: sabreException[code], Message: message, Header: header, }) @@ -98,44 +130,47 @@ type ErrorXML struct { Header string `xml:"s:header,omitempty"` } -// ErrorInvalidPropfind is an invalid propfind error -var ErrorInvalidPropfind = errors.New("webdav: invalid propfind") - -// ErrInvalidProppatch is an invalid proppatch error -var ErrInvalidProppatch = errors.New("webdav: invalid proppatch") +var ( + // ErrInvalidDepth is an invalid depth header error + ErrInvalidDepth = errors.New("webdav: invalid depth") + // ErrInvalidPropfind is an invalid propfind error + ErrInvalidPropfind = errors.New("webdav: invalid propfind") + // ErrInvalidProppatch is an invalid proppatch error + ErrInvalidProppatch = errors.New("webdav: invalid proppatch") + // ErrInvalidLockInfo is an invalid lock error + ErrInvalidLockInfo = errors.New("webdav: invalid lock info") + // ErrUnsupportedLockInfo is an unsupported lock error + ErrUnsupportedLockInfo = errors.New("webdav: unsupported lock info") + // ErrInvalidTimeout is an invalid timeout error + ErrInvalidTimeout = errors.New("webdav: invalid timeout") + // ErrInvalidIfHeader is an invalid if header error + ErrInvalidIfHeader = errors.New("webdav: invalid If header") + // ErrUnsupportedMethod is an unsupported method error + ErrUnsupportedMethod = errors.New("webdav: unsupported method") + // ErrInvalidLockToken is an invalid lock token error + ErrInvalidLockToken = errors.New("webdav: invalid lock token") + // ErrConfirmationFailed is returned by a LockSystem's Confirm method. + ErrConfirmationFailed = errors.New("webdav: confirmation failed") + // ErrForbidden is returned by a LockSystem's Unlock method. + ErrForbidden = errors.New("webdav: forbidden") + // ErrLocked is returned by a LockSystem's Create, Refresh and Unlock methods. + ErrLocked = errors.New("webdav: locked") + // ErrNoSuchLock is returned by a LockSystem's Refresh and Unlock methods. + ErrNoSuchLock = errors.New("webdav: no such lock") + // ErrNotImplemented is returned when hitting not implemented code paths + ErrNotImplemented = errors.New("webdav: not implemented") +) // HandleErrorStatus checks the status code, logs a Debug or Error level message // and writes an appropriate http status func HandleErrorStatus(log *zerolog.Logger, w http.ResponseWriter, s *rpc.Status) { - switch s.Code { - case rpc.Code_CODE_OK: - log.Debug().Interface("status", s).Msg("ok") - w.WriteHeader(http.StatusOK) - case rpc.Code_CODE_NOT_FOUND: - log.Debug().Interface("status", s).Msg("resource not found") - w.WriteHeader(http.StatusNotFound) - case rpc.Code_CODE_PERMISSION_DENIED: - log.Debug().Interface("status", s).Msg("permission denied") - w.WriteHeader(http.StatusForbidden) - case rpc.Code_CODE_UNAUTHENTICATED: - log.Debug().Interface("status", s).Msg("unauthenticated") - w.WriteHeader(http.StatusUnauthorized) - case rpc.Code_CODE_INVALID_ARGUMENT: - log.Debug().Interface("status", s).Msg("bad request") - w.WriteHeader(http.StatusBadRequest) - case rpc.Code_CODE_UNIMPLEMENTED: - log.Debug().Interface("status", s).Msg("not implemented") - w.WriteHeader(http.StatusNotImplemented) - case rpc.Code_CODE_INSUFFICIENT_STORAGE: - log.Debug().Interface("status", s).Msg("insufficient storage") - w.WriteHeader(http.StatusInsufficientStorage) - case rpc.Code_CODE_FAILED_PRECONDITION: - log.Debug().Interface("status", s).Msg("destination does not exist") - w.WriteHeader(http.StatusConflict) - default: - log.Error().Interface("status", s).Msg("grpc request failed") - w.WriteHeader(http.StatusInternalServerError) + hsc := status.HTTPStatusFromCode(s.Code) + if hsc == http.StatusInternalServerError { + log.Error().Interface("status", s).Int("code", hsc).Msg(http.StatusText(hsc)) + } else { + log.Debug().Interface("status", s).Int("code", hsc).Msg(http.StatusText(hsc)) } + w.WriteHeader(hsc) } // HandleWebdavError checks the status code, logs an error and creates a webdav response body diff --git a/internal/http/services/owncloud/ocdav/if.go b/internal/http/services/owncloud/ocdav/if.go new file mode 100644 index 00000000000..c331fcbc988 --- /dev/null +++ b/internal/http/services/owncloud/ocdav/if.go @@ -0,0 +1,193 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ocdav + +// copy of https://github.com/golang/net/blob/master/webdav/if.go + +// The If header is covered by Section 10.4. +// http://www.webdav.org/specs/rfc4918.html#HEADER_If + +import ( + "strings" +) + +// ifHeader is a disjunction (OR) of ifLists. +type ifHeader struct { + lists []ifList +} + +// ifList is a conjunction (AND) of Conditions, and an optional resource tag. +type ifList struct { + resourceTag string + conditions []Condition +} + +// parseIfHeader parses the "If: foo bar" HTTP header. The httpHeader string +// should omit the "If:" prefix and have any "\r\n"s collapsed to a " ", as is +// returned by req.Header.Get("If") for a http.Request req. +func parseIfHeader(httpHeader string) (h ifHeader, ok bool) { + s := strings.TrimSpace(httpHeader) + switch tokenType, _, _ := lex(s); tokenType { + case '(': + return parseNoTagLists(s) + case angleTokenType: + return parseTaggedLists(s) + default: + return ifHeader{}, false + } +} + +func parseNoTagLists(s string) (h ifHeader, ok bool) { + for { + l, remaining, ok := parseList(s) + if !ok { + return ifHeader{}, false + } + h.lists = append(h.lists, l) + if remaining == "" { + return h, true + } + s = remaining + } +} + +func parseTaggedLists(s string) (h ifHeader, ok bool) { + resourceTag, n := "", 0 + for first := true; ; first = false { + tokenType, tokenStr, remaining := lex(s) + switch tokenType { + case angleTokenType: + if !first && n == 0 { + return ifHeader{}, false + } + resourceTag, n = tokenStr, 0 + s = remaining + case '(': + n++ + l, remaining, ok := parseList(s) + if !ok { + return ifHeader{}, false + } + l.resourceTag = resourceTag + h.lists = append(h.lists, l) + if remaining == "" { + return h, true + } + s = remaining + default: + return ifHeader{}, false + } + } +} + +func parseList(s string) (l ifList, remaining string, ok bool) { + tokenType, _, s := lex(s) + if tokenType != '(' { + return ifList{}, "", false + } + for { + tokenType, _, remaining = lex(s) + if tokenType == ')' { + if len(l.conditions) == 0 { + return ifList{}, "", false + } + return l, remaining, true + } + c, remaining, ok := parseCondition(s) + if !ok { + return ifList{}, "", false + } + l.conditions = append(l.conditions, c) + s = remaining + } +} + +func parseCondition(s string) (c Condition, remaining string, ok bool) { + tokenType, tokenStr, s := lex(s) + if tokenType == notTokenType { + c.Not = true + tokenType, tokenStr, s = lex(s) + } + switch tokenType { + case strTokenType, angleTokenType: + c.Token = tokenStr + case squareTokenType: + c.ETag = tokenStr + default: + return Condition{}, "", false + } + return c, s, true +} + +// Single-rune tokens like '(' or ')' have a token type equal to their rune. +// All other tokens have a negative token type. +const ( + errTokenType = rune(-1) + eofTokenType = rune(-2) + strTokenType = rune(-3) + notTokenType = rune(-4) + angleTokenType = rune(-5) + squareTokenType = rune(-6) +) + +func lex(s string) (tokenType rune, tokenStr string, remaining string) { + // The net/textproto Reader that parses the HTTP header will collapse + // Linear White Space that spans multiple "\r\n" lines to a single " ", + // so we don't need to look for '\r' or '\n'. + for len(s) > 0 && (s[0] == '\t' || s[0] == ' ') { + s = s[1:] + } + if len(s) == 0 { + return eofTokenType, "", "" + } + i := 0 +loop: + for ; i < len(s); i++ { + switch s[i] { + case '\t', ' ', '(', ')', '<', '>', '[', ']': + break loop + } + } + + if i != 0 { + tokenStr, remaining = s[:i], s[i:] + if tokenStr == "Not" { + return notTokenType, "", remaining + } + return strTokenType, tokenStr, remaining + } + + j := 0 + switch s[0] { + case '<': + j, tokenType = strings.IndexByte(s, '>'), angleTokenType + case '[': + j, tokenType = strings.IndexByte(s, ']'), squareTokenType + default: + return rune(s[0]), "", s[1:] + } + if j < 0 { + return errTokenType, "", "" + } + return tokenType, s[1:j], s[j+1:] +} diff --git a/internal/http/services/owncloud/ocdav/if_test.go b/internal/http/services/owncloud/ocdav/if_test.go new file mode 100644 index 00000000000..fcdcb6780e0 --- /dev/null +++ b/internal/http/services/owncloud/ocdav/if_test.go @@ -0,0 +1,338 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package ocdav + +// copy of https://github.com/golang/net/blob/master/webdav/if_test.go + +import ( + "reflect" + "strings" + "testing" +) + +func TestParseIfHeader(t *testing.T) { + // The "section x.y.z" test cases come from section x.y.z of the spec at + // http://www.webdav.org/specs/rfc4918.html + testCases := []struct { + desc string + input string + want ifHeader + }{{ + "bad: empty", + ``, + ifHeader{}, + }, { + "bad: no parens", + `foobar`, + ifHeader{}, + }, { + "bad: empty list #1", + `()`, + ifHeader{}, + }, { + "bad: empty list #2", + `(a) (b c) () (d)`, + ifHeader{}, + }, { + "bad: no list after resource #1", + ``, + ifHeader{}, + }, { + "bad: no list after resource #2", + ` (a)`, + ifHeader{}, + }, { + "bad: no list after resource #3", + ` (a) (b) `, + ifHeader{}, + }, { + "bad: no-tag-list followed by tagged-list", + `(a) (b) (c)`, + ifHeader{}, + }, { + "bad: unfinished list", + `(a`, + ifHeader{}, + }, { + "bad: unfinished ETag", + `([b`, + ifHeader{}, + }, { + "bad: unfinished Notted list", + `(Not a`, + ifHeader{}, + }, { + "bad: double Not", + `(Not Not a)`, + ifHeader{}, + }, { + "good: one list with a Token", + `(a)`, + ifHeader{ + lists: []ifList{{ + conditions: []Condition{{ + Token: `a`, + }}, + }}, + }, + }, { + "good: one list with an ETag", + `([a])`, + ifHeader{ + lists: []ifList{{ + conditions: []Condition{{ + ETag: `a`, + }}, + }}, + }, + }, { + "good: one list with three Nots", + `(Not a Not b Not [d])`, + ifHeader{ + lists: []ifList{{ + conditions: []Condition{{ + Not: true, + Token: `a`, + }, { + Not: true, + Token: `b`, + }, { + Not: true, + ETag: `d`, + }}, + }}, + }, + }, { + "good: two lists", + `(a) (b)`, + ifHeader{ + lists: []ifList{{ + conditions: []Condition{{ + Token: `a`, + }}, + }, { + conditions: []Condition{{ + Token: `b`, + }}, + }}, + }, + }, { + "good: two Notted lists", + `(Not a) (Not b)`, + ifHeader{ + lists: []ifList{{ + conditions: []Condition{{ + Not: true, + Token: `a`, + }}, + }, { + conditions: []Condition{{ + Not: true, + Token: `b`, + }}, + }}, + }, + }, { + "section 7.5.1", + ` + ()`, + ifHeader{ + lists: []ifList{{ + resourceTag: `http://www.example.com/users/f/fielding/index.html`, + conditions: []Condition{{ + Token: `urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6`, + }}, + }}, + }, + }, { + "section 7.5.2 #1", + `()`, + ifHeader{ + lists: []ifList{{ + conditions: []Condition{{ + Token: `urn:uuid:150852e2-3847-42d5-8cbe-0f4f296f26cf`, + }}, + }}, + }, + }, { + "section 7.5.2 #2", + ` + ()`, + ifHeader{ + lists: []ifList{{ + resourceTag: `http://example.com/locked/`, + conditions: []Condition{{ + Token: `urn:uuid:150852e2-3847-42d5-8cbe-0f4f296f26cf`, + }}, + }}, + }, + }, { + "section 7.5.2 #3", + ` + ()`, + ifHeader{ + lists: []ifList{{ + resourceTag: `http://example.com/locked/member`, + conditions: []Condition{{ + Token: `urn:uuid:150852e2-3847-42d5-8cbe-0f4f296f26cf`, + }}, + }}, + }, + }, { + "section 9.9.6", + `() + ()`, + ifHeader{ + lists: []ifList{{ + conditions: []Condition{{ + Token: `urn:uuid:fe184f2e-6eec-41d0-c765-01adc56e6bb4`, + }}, + }, { + conditions: []Condition{{ + Token: `urn:uuid:e454f3f3-acdc-452a-56c7-00a5c91e4b77`, + }}, + }}, + }, + }, { + "section 9.10.8", + `()`, + ifHeader{ + lists: []ifList{{ + conditions: []Condition{{ + Token: `urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4`, + }}, + }}, + }, + }, { + "section 10.4.6", + `( + ["I am an ETag"]) + (["I am another ETag"])`, + ifHeader{ + lists: []ifList{{ + conditions: []Condition{{ + Token: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`, + }, { + ETag: `"I am an ETag"`, + }}, + }, { + conditions: []Condition{{ + ETag: `"I am another ETag"`, + }}, + }}, + }, + }, { + "section 10.4.7", + `(Not + )`, + ifHeader{ + lists: []ifList{{ + conditions: []Condition{{ + Not: true, + Token: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`, + }, { + Token: `urn:uuid:58f202ac-22cf-11d1-b12d-002035b29092`, + }}, + }}, + }, + }, { + "section 10.4.8", + `() + (Not )`, + ifHeader{ + lists: []ifList{{ + conditions: []Condition{{ + Token: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`, + }}, + }, { + conditions: []Condition{{ + Not: true, + Token: `DAV:no-lock`, + }}, + }}, + }, + }, { + "section 10.4.9", + ` + ( + [W/"A weak ETag"]) (["strong ETag"])`, + ifHeader{ + lists: []ifList{{ + resourceTag: `/resource1`, + conditions: []Condition{{ + Token: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`, + }, { + ETag: `W/"A weak ETag"`, + }}, + }, { + resourceTag: `/resource1`, + conditions: []Condition{{ + ETag: `"strong ETag"`, + }}, + }}, + }, + }, { + "section 10.4.10", + ` + ()`, + ifHeader{ + lists: []ifList{{ + resourceTag: `http://www.example.com/specs/`, + conditions: []Condition{{ + Token: `urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2`, + }}, + }}, + }, + }, { + "section 10.4.11 #1", + ` (["4217"])`, + ifHeader{ + lists: []ifList{{ + resourceTag: `/specs/rfc2518.doc`, + conditions: []Condition{{ + ETag: `"4217"`, + }}, + }}, + }, + }, { + "section 10.4.11 #2", + ` (Not ["4217"])`, + ifHeader{ + lists: []ifList{{ + resourceTag: `/specs/rfc2518.doc`, + conditions: []Condition{{ + Not: true, + ETag: `"4217"`, + }}, + }}, + }, + }} + + for _, tc := range testCases { + got, ok := parseIfHeader(strings.ReplaceAll(tc.input, "\n", "")) + if gotEmpty := reflect.DeepEqual(got, ifHeader{}); gotEmpty == ok { + t.Errorf("%s: should be different: empty header == %t, ok == %t", tc.desc, gotEmpty, ok) + continue + } + if !reflect.DeepEqual(got, tc.want) { + t.Errorf("%s:\ngot %v\nwant %v", tc.desc, got, tc.want) + continue + } + } +} diff --git a/internal/http/services/owncloud/ocdav/lock.go b/internal/http/services/owncloud/ocdav/lock.go deleted file mode 100644 index 65f43cf64c2..00000000000 --- a/internal/http/services/owncloud/ocdav/lock.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2018-2021 CERN -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -package ocdav - -import ( - "net/http" - - "github.com/cs3org/reva/pkg/appctx" -) - -// TODO(jfd) implement lock -func (s *svc) handleLock(w http.ResponseWriter, r *http.Request, ns string) { - log := appctx.GetLogger(r.Context()) - xml := ` - - - - - Second-604800 - Infinity - - opaquelocktoken:00000000-0000-0000-0000-000000000000 - - - - ` - - w.Header().Set("Content-Type", "text/xml; charset=\"utf-8\"") - w.Header().Set("Lock-Token", - "opaquelocktoken:00000000-0000-0000-0000-000000000000") - _, err := w.Write([]byte(xml)) - if err != nil { - log.Err(err).Msg("error writing response") - } -} diff --git a/internal/http/services/owncloud/ocdav/locks.go b/internal/http/services/owncloud/ocdav/locks.go new file mode 100644 index 00000000000..daee6d8dba1 --- /dev/null +++ b/internal/http/services/owncloud/ocdav/locks.go @@ -0,0 +1,608 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package ocdav + +import ( + "context" + "encoding/xml" + "fmt" + "io" + "net/http" + "path" + "strconv" + "strings" + "time" + + 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" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/props" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/spacelookup" + "github.com/cs3org/reva/pkg/appctx" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" + rtrace "github.com/cs3org/reva/pkg/trace" + "github.com/google/uuid" + "go.opentelemetry.io/otel/attribute" +) + +// Most of this is taken from https://github.com/golang/net/blob/master/webdav/lock.go + +// From RFC4918 http://www.webdav.org/specs/rfc4918.html#lock-tokens +// This specification encourages servers to create Universally Unique Identifiers (UUIDs) for lock tokens, +// and to use the URI form defined by "A Universally Unique Identifier (UUID) URN Namespace" ([RFC4122]). +// However, servers are free to use any URI (e.g., from another scheme) so long as it meets the uniqueness +// requirements. For example, a valid lock token might be constructed using the "opaquelocktoken" scheme +// defined in Appendix C. +// +// Example: "urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6" +// +// we stick to the recommendation and use the URN Namespace +const lockTokenPrefix = "urn:uuid:" + +// TODO(jfd) implement lock +// see Web Distributed Authoring and Versioning (WebDAV) Locking Protocol: +// https://www.greenbytes.de/tech/webdav/draft-reschke-webdav-locking-latest.html +// Webdav supports a Depth: infinity lock, wopi only needs locks on files + +// https://www.greenbytes.de/tech/webdav/draft-reschke-webdav-locking-latest.html#write.locks.and.the.if.request.header +// [...] a lock token MUST be submitted in the If header for all locked resources +// that a method may interact with or the method MUST fail. [...] +/* + COPY /~fielding/index.html HTTP/1.1 + Host: example.com + Destination: http://example.com/users/f/fielding/index.html + If: + () +*/ + +// http://www.webdav.org/specs/rfc4918.html#ELEMENT_lockinfo +type lockInfo struct { + XMLName xml.Name `xml:"lockinfo"` + Exclusive *struct{} `xml:"lockscope>exclusive"` + Shared *struct{} `xml:"lockscope>shared"` + Write *struct{} `xml:"locktype>write"` + Owner owner `xml:"owner"` +} + +// http://www.webdav.org/specs/rfc4918.html#ELEMENT_owner +type owner struct { + InnerXML string `xml:",innerxml"` +} + +// Condition can match a WebDAV resource, based on a token or ETag. +// Exactly one of Token and ETag should be non-empty. +type Condition struct { + Not bool + Token string + ETag string +} + +// LockSystem manages access to a collection of named resources. The elements +// in a lock name are separated by slash ('/', U+002F) characters, regardless +// of host operating system convention. +type LockSystem interface { + // Confirm confirms that the caller can claim all of the locks specified by + // the given conditions, and that holding the union of all of those locks + // gives exclusive access to all of the named resources. Up to two resources + // can be named. Empty names are ignored. + // + // Exactly one of release and err will be non-nil. If release is non-nil, + // all of the requested locks are held until release is called. Calling + // release does not unlock the lock, in the WebDAV UNLOCK sense, but once + // Confirm has confirmed that a lock claim is valid, that lock cannot be + // Confirmed again until it has been released. + // + // If Confirm returns ErrConfirmationFailed then the Handler will continue + // to try any other set of locks presented (a WebDAV HTTP request can + // present more than one set of locks). If it returns any other non-nil + // error, the Handler will write a "500 Internal Server Error" HTTP status. + Confirm(ctx context.Context, now time.Time, name0, name1 string, conditions ...Condition) (release func(), err error) + + // Create creates a lock with the given depth, duration, owner and root + // (name). The depth will either be negative (meaning infinite) or zero. + // + // If Create returns ErrLocked then the Handler will write a "423 Locked" + // HTTP status. If it returns any other non-nil error, the Handler will + // write a "500 Internal Server Error" HTTP status. + // + // See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.10.6 for + // when to use each error. + // + // The token returned identifies the created lock. It should be an absolute + // URI as defined by RFC 3986, Section 4.3. In particular, it should not + // contain whitespace. + Create(ctx context.Context, now time.Time, details LockDetails) (token string, err error) + + // Refresh refreshes the lock with the given token. + // + // If Refresh returns ErrLocked then the Handler will write a "423 Locked" + // HTTP Status. If Refresh returns ErrNoSuchLock then the Handler will write + // a "412 Precondition Failed" HTTP Status. If it returns any other non-nil + // error, the Handler will write a "500 Internal Server Error" HTTP status. + // + // See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.10.6 for + // when to use each error. + Refresh(ctx context.Context, now time.Time, token string, duration time.Duration) (LockDetails, error) + + // Unlock unlocks the lock with the given token. + // + // If Unlock returns ErrForbidden then the Handler will write a "403 + // Forbidden" HTTP Status. If Unlock returns ErrLocked then the Handler + // will write a "423 Locked" HTTP status. If Unlock returns ErrNoSuchLock + // then the Handler will write a "409 Conflict" HTTP Status. If it returns + // any other non-nil error, the Handler will write a "500 Internal Server + // Error" HTTP status. + // + // See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.11.1 for + // when to use each error. + Unlock(ctx context.Context, now time.Time, ref *provider.Reference, token string) error +} + +// NewCS3LS returns a new CS3 based LockSystem. +func NewCS3LS(c gateway.GatewayAPIClient) LockSystem { + return &cs3LS{ + client: c, + } +} + +type cs3LS struct { + client gateway.GatewayAPIClient +} + +func (cls *cs3LS) Confirm(ctx context.Context, now time.Time, name0, name1 string, conditions ...Condition) (func(), error) { + return nil, errors.ErrNotImplemented +} + +func (cls *cs3LS) Create(ctx context.Context, now time.Time, details LockDetails) (string, error) { + // always assume depth infinity? + /* + if !details.ZeroDepth { + The CS3 Lock api currently has no depth property, it only locks single resources + return "", errors.ErrUnsupportedLockInfo + } + */ + + // Having a lock token provides no special access rights. Anyone can find out anyone + // else's lock token by performing lock discovery. Locks must be enforced based upon + // whatever authentication mechanism is used by the server, not based on the secrecy + // of the token values. + // see: http://www.webdav.org/specs/rfc2518.html#n-lock-tokens + token := uuid.New() + + r := &provider.SetLockRequest{ + Ref: details.Root, + Lock: &provider.Lock{ + Type: provider.LockType_LOCK_TYPE_EXCL, + User: details.UserID, // no way to set an app lock? TODO maybe via the ownerxml + //AppName: , // TODO use a urn scheme? + LockId: lockTokenPrefix + token.String(), // can be a token or a Coded-URL + }, + } + if details.Duration > 0 { + expiration := time.Now().UTC().Add(details.Duration) + r.Lock.Expiration = &types.Timestamp{ + Seconds: uint64(expiration.Unix()), + Nanos: uint32(expiration.Nanosecond()), + } + } + res, err := cls.client.SetLock(ctx, r) + if err != nil { + return "", err + } + if res.Status.Code != rpc.Code_CODE_OK { + return "", errtypes.NewErrtypeFromStatus(res.Status) + } + return lockTokenPrefix + token.String(), nil +} + +func (cls *cs3LS) Refresh(ctx context.Context, now time.Time, token string, duration time.Duration) (LockDetails, error) { + return LockDetails{}, errors.ErrNotImplemented +} +func (cls *cs3LS) Unlock(ctx context.Context, now time.Time, ref *provider.Reference, token string) error { + r := &provider.UnlockRequest{ + Ref: ref, + Lock: &provider.Lock{ + LockId: token, // can be a token or a Coded-URL + }, + } + res, err := cls.client.Unlock(ctx, r) + if err != nil { + return err + } + if res.Status.Code != rpc.Code_CODE_OK { + return errtypes.NewErrtypeFromStatus(res.Status) + } + return nil +} + +// LockDetails are a lock's metadata. +type LockDetails struct { + // Root is the root resource name being locked. For a zero-depth lock, the + // root is the only resource being locked. + Root *provider.Reference + // Duration is the lock timeout. A negative duration means infinite. + Duration time.Duration + // OwnerXML is the verbatim XML given in a LOCK HTTP request. + // + // TODO: does the "verbatim" nature play well with XML namespaces? + // Does the OwnerXML field need to have more structure? See + // https://codereview.appspot.com/175140043/#msg2 + OwnerXML string + UserID *userpb.UserId + // ZeroDepth is whether the lock has zero depth. If it does not have zero + // depth, it has infinite depth. + ZeroDepth bool +} + +func readLockInfo(r io.Reader) (li lockInfo, status int, err error) { + c := &countingReader{r: r} + if err = xml.NewDecoder(c).Decode(&li); err != nil { + if err == io.EOF { + if c.n == 0 { + // An empty body means to refresh the lock. + // http://www.webdav.org/specs/rfc4918.html#refreshing-locks + return lockInfo{}, 0, nil + } + err = errors.ErrInvalidLockInfo + } + return lockInfo{}, http.StatusBadRequest, err + } + // We only support exclusive (non-shared) write locks. In practice, these are + // the only types of locks that seem to matter. + // We are ignoring the any properties in the lock details, and assume an exclusive write lock is requested. + // https://datatracker.ietf.org/doc/html/rfc4918#section-7 only describes write locks + // + // if li.Exclusive == nil || li.Shared != nil { + // return lockInfo{}, http.StatusNotImplemented, errors.ErrUnsupportedLockInfo + // } + // what should we return if the user requests a shared lock? or leaves out the locktype? the testsuite will only send the property lockscope, not locktype + // the oc tests cover both shared and exclusive locks. What is the WOPI lock? a shared or an exclusive lock? + // since it is issued by a service it seems to be an exclusive lock. + // the owner could be a link to the collaborative app ... to join the session + return li, 0, nil +} + +type countingReader struct { + n int + r io.Reader +} + +func (c *countingReader) Read(p []byte) (int, error) { + n, err := c.r.Read(p) + c.n += n + return n, err +} + +const infiniteTimeout = -1 + +// parseTimeout parses the Timeout HTTP header, as per section 10.7. If s is +// empty, an infiniteTimeout is returned. +func parseTimeout(s string) (time.Duration, error) { + if s == "" { + return infiniteTimeout, nil + } + if i := strings.IndexByte(s, ','); i >= 0 { + s = s[:i] + } + s = strings.TrimSpace(s) + if s == "Infinite" { + return infiniteTimeout, nil + } + const pre = "Second-" + if !strings.HasPrefix(s, pre) { + return 0, errors.ErrInvalidTimeout + } + s = s[len(pre):] + if s == "" || s[0] < '0' || '9' < s[0] { + return 0, errors.ErrInvalidTimeout + } + n, err := strconv.ParseInt(s, 10, 64) + if err != nil || 1<<32-1 < n { + return 0, errors.ErrInvalidTimeout + } + return time.Duration(n) * time.Second, nil +} + +const ( + infiniteDepth = -1 + invalidDepth = -2 +) + +// parseDepth maps the strings "0", "1" and "infinity" to 0, 1 and +// infiniteDepth. Parsing any other string returns invalidDepth. +// +// Different WebDAV methods have further constraints on valid depths: +// - PROPFIND has no further restrictions, as per section 9.1. +// - COPY accepts only "0" or "infinity", as per section 9.8.3. +// - MOVE accepts only "infinity", as per section 9.9.2. +// - LOCK accepts only "0" or "infinity", as per section 9.10.3. +// These constraints are enforced by the handleXxx methods. +func parseDepth(s string) int { + switch s { + case "0": + return 0 + case "1": + return 1 + case "infinity": + return infiniteDepth + } + return invalidDepth +} + +/* + the oc 10 wopi app code locks like this: + + $storage->lockNodePersistent($file->getInternalPath(), [ + 'token' => $wopiLock, + 'owner' => "{$user->getDisplayName()} via Office Online" + ]); + + if owner is empty it defaults to '{displayname} ({email})', which is not a url ... but ... shrug + + The LockManager also defaults to exclusive locks: + + $scope = ILock::LOCK_SCOPE_EXCLUSIVE; + if (isset($lockInfo['scope'])) { + $scope = $lockInfo['scope']; + } +*/ +func (s *svc) handleLock(w http.ResponseWriter, r *http.Request, ns string) (retStatus int, retErr error) { + ctx, span := rtrace.Provider.Tracer("reva").Start(r.Context(), fmt.Sprintf("%s %v", r.Method, r.URL.Path)) + defer span.End() + + span.SetAttributes(attribute.String("component", "ocdav")) + + fn := path.Join(ns, r.URL.Path) // TODO do we still need to jail if we query the registry about the spaces? + + client, err := s.getClient() + if err != nil { + return http.StatusInternalServerError, err + } + + // TODO instead of using a string namespace ns pass in the space with the request? + ref, cs3Status, err := spacelookup.LookupReferenceForPath(ctx, client, fn) + if err != nil { + return http.StatusInternalServerError, err + } + if cs3Status.Code != rpc.Code_CODE_OK { + return http.StatusInternalServerError, errtypes.NewErrtypeFromStatus(cs3Status) + } + + return s.lockReference(ctx, w, r, ref) +} + +func (s *svc) handleSpacesLock(w http.ResponseWriter, r *http.Request, spaceID string) (retStatus int, retErr error) { + ctx, span := rtrace.Provider.Tracer("reva").Start(r.Context(), fmt.Sprintf("%s %v", r.Method, r.URL.Path)) + defer span.End() + + span.SetAttributes(attribute.String("component", "ocdav")) + + client, err := s.getClient() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + // retrieve a specific storage space + space, cs3Status, err := spacelookup.LookUpStorageSpaceByID(ctx, client, spaceID) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + if cs3Status.Code != rpc.Code_CODE_OK { + return http.StatusInternalServerError, errtypes.NewErrtypeFromStatus(cs3Status) + } + + ref := spacelookup.MakeRelativeReference(space, r.URL.Path, true) + + return s.lockReference(ctx, w, r, ref) +} + +func (s *svc) lockReference(ctx context.Context, w http.ResponseWriter, r *http.Request, ref *provider.Reference) (retStatus int, retErr error) { + sublog := appctx.GetLogger(ctx).With().Interface("ref", ref).Logger() + duration, err := parseTimeout(r.Header.Get(net.HeaderTimeout)) + if err != nil { + return http.StatusBadRequest, errors.ErrInvalidTimeout + } + + li, status, err := readLockInfo(r.Body) + if err != nil { + return status, errors.ErrInvalidLockInfo + } + + u := ctxpkg.ContextMustGetUser(ctx) + token, ld, now, created := "", LockDetails{UserID: u.Id, Root: ref, Duration: duration}, time.Now(), false + if li == (lockInfo{}) { + // An empty lockInfo means to refresh the lock. + ih, ok := parseIfHeader(r.Header.Get(net.HeaderIf)) + if !ok { + return http.StatusBadRequest, errors.ErrInvalidIfHeader + } + if len(ih.lists) == 1 && len(ih.lists[0].conditions) == 1 { + token = ih.lists[0].conditions[0].Token + } + if token == "" { + return http.StatusBadRequest, errors.ErrInvalidLockToken + } + ld, err = s.LockSystem.Refresh(ctx, now, token, duration) + if err != nil { + if err == errors.ErrNoSuchLock { + return http.StatusPreconditionFailed, err + } + return http.StatusInternalServerError, err + } + + } else { + // Section 9.10.3 says that "If no Depth header is submitted on a LOCK request, + // then the request MUST act as if a "Depth:infinity" had been submitted." + depth := infiniteDepth + if hdr := r.Header.Get(net.HeaderDepth); hdr != "" { + depth = parseDepth(hdr) + if depth != 0 && depth != infiniteDepth { + // Section 9.10.3 says that "Values other than 0 or infinity must not be + // used with the Depth header on a LOCK method". + return http.StatusBadRequest, errors.ErrInvalidDepth + } + } + /* our url path has been shifted, so we don't need to do this? + reqPath, status, err := h.stripPrefix(r.URL.Path) + if err != nil { + return status, err + } + */ + // TODO look up username and email + // if li.Owner.InnerXML == "" { + // // PHP version: 'owner' => "{$user->getDisplayName()} via Office Online" + // ld.OwnerXML = ld.UserID.OpaqueId + // } + ld.OwnerXML = li.Owner.InnerXML // TODO optional, should be a URL + ld.ZeroDepth = depth == 0 + + //TODO: @jfd the code tries to create a lock for a file that may not even exist, + // should we do that in the decomposedfs as well? the node does not exist + // this actually is a name based lock ... ugh + token, err = s.LockSystem.Create(ctx, now, ld) + if err != nil { + if _, ok := err.(errtypes.Locked); ok { + return http.StatusLocked, err + } + return http.StatusInternalServerError, err + } + + defer func() { + if retErr != nil { + if err := s.LockSystem.Unlock(ctx, now, ref, token); err != nil { + appctx.GetLogger(ctx).Error().Err(err).Interface("lock", ld).Msg("could not unlock after failed lock") + } + } + }() + + // Create the resource if it didn't previously exist. + // TODO use sdk to stat? + /* + if _, err := s.FileSystem.Stat(ctx, reqPath); err != nil { + f, err := s.FileSystem.OpenFile(ctx, reqPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) + if err != nil { + // TODO: detect missing intermediate dirs and return http.StatusConflict? + return http.StatusInternalServerError, err + } + f.Close() + created = true + } + */ + // http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the + // Lock-Token value is a Coded-URL. We add angle brackets. + w.Header().Set("Lock-Token", "<"+token+">") + } + + w.Header().Set("Content-Type", "application/xml; charset=utf-8") + if created { + // This is "w.WriteHeader(http.StatusCreated)" and not "return + // http.StatusCreated, nil" because we write our own (XML) response to w + // and Handler.ServeHTTP would otherwise write "Created". + w.WriteHeader(http.StatusCreated) + } + n, err := writeLockInfo(w, token, ld) + if err != nil { + sublog.Err(err).Int("bytes_written", n).Msg("error writing response") + } + return 0, nil +} + +func writeLockInfo(w io.Writer, token string, ld LockDetails) (int, error) { + depth := "infinity" + if ld.ZeroDepth { + depth = "0" + } + href := ld.Root.Path // FIXME add base url and space? + + lockdiscovery := strings.Builder{} + lockdiscovery.WriteString(xml.Header) + lockdiscovery.WriteString("\n") + lockdiscovery.WriteString(" \n") + lockdiscovery.WriteString(" \n") + lockdiscovery.WriteString(fmt.Sprintf(" %s\n", depth)) + if ld.OwnerXML != "" { + lockdiscovery.WriteString(fmt.Sprintf(" %s\n", ld.OwnerXML)) + } + if ld.Duration > 0 { + timeout := ld.Duration / time.Second + lockdiscovery.WriteString(fmt.Sprintf(" Second-%d\n", timeout)) + } else { + lockdiscovery.WriteString(" Infinite\n") + } + if token != "" { + lockdiscovery.WriteString(fmt.Sprintf(" %s\n", props.Escape(token))) + } + if href != "" { + lockdiscovery.WriteString(fmt.Sprintf(" %s\n", props.Escape(href))) + } + lockdiscovery.WriteString("") + + return fmt.Fprint(w, lockdiscovery.String()) +} + +func (s *svc) handleUnlock(w http.ResponseWriter, r *http.Request, ns string) (status int, err error) { + ctx, span := rtrace.Provider.Tracer("reva").Start(r.Context(), fmt.Sprintf("%s %v", r.Method, r.URL.Path)) + defer span.End() + + span.SetAttributes(attribute.String("component", "ocdav")) + + fn := path.Join(ns, r.URL.Path) // TODO do we still need to jail if we query the registry about the spaces? + + client, err := s.getClient() + if err != nil { + return http.StatusInternalServerError, err + } + + // TODO instead of using a string namespace ns pass in the space with the request? + ref, cs3Status, err := spacelookup.LookupReferenceForPath(ctx, client, fn) + if err != nil { + return http.StatusInternalServerError, err + } + if cs3Status.Code != rpc.Code_CODE_OK { + return http.StatusInternalServerError, errtypes.NewErrtypeFromStatus(cs3Status) + } + + // http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the + // Lock-Token value should be a Coded-URL OR a token. We strip its angle brackets. + t := r.Header.Get(net.HeaderLockToken) + if len(t) > 2 && t[0] == '<' && t[len(t)-1] == '>' { + t = t[1 : len(t)-1] + } + + switch err = s.LockSystem.Unlock(r.Context(), time.Now(), ref, t); err { + case nil: + return http.StatusNoContent, err + case errors.ErrForbidden: + return http.StatusForbidden, err + case errors.ErrLocked: + return http.StatusLocked, err + case errors.ErrNoSuchLock: + return http.StatusConflict, err + default: + return http.StatusInternalServerError, err + } +} diff --git a/internal/http/services/owncloud/ocdav/mkcol.go b/internal/http/services/owncloud/ocdav/mkcol.go index 23d1b3fd59b..2d55f1b3e2d 100644 --- a/internal/http/services/owncloud/ocdav/mkcol.go +++ b/internal/http/services/owncloud/ocdav/mkcol.go @@ -69,7 +69,7 @@ func (s *svc) handlePathMkcol(w http.ResponseWriter, r *http.Request, ns string) sublog.Info().Err(err).Str("path", fn).Interface("code", sr.Status.Code).Msg("response code for stat was unexpected") // tests want this errorcode. StatusConflict would be more logical w.WriteHeader(http.StatusMethodNotAllowed) - b, err := errors.Marshal(errors.SabredavMethodNotAllowed, "The resource you tried to create already exists", "") + b, err := errors.Marshal(http.StatusMethodNotAllowed, "The resource you tried to create already exists", "") errors.HandleWebdavError(&sublog, w, b, err) return } @@ -159,7 +159,7 @@ func (s *svc) handleMkcol(ctx context.Context, w http.ResponseWriter, r *http.Re // all ancestors must already exist, or the method must fail // with a 409 (Conflict) status code. w.WriteHeader(http.StatusConflict) - b, err := errors.Marshal(errors.SabredavNotFound, "Parent node does not exist", "") + b, err := errors.Marshal(http.StatusConflict, "Parent node does not exist", "") errors.HandleWebdavError(&log, w, b, err) } else { errors.HandleErrorStatus(&log, w, parentStatRes.Status) @@ -179,7 +179,7 @@ func (s *svc) handleMkcol(ctx context.Context, w http.ResponseWriter, r *http.Re if statRes.Status.Code != rpc.Code_CODE_NOT_FOUND { if statRes.Status.Code == rpc.Code_CODE_OK { w.WriteHeader(http.StatusMethodNotAllowed) // 405 if it already exists - b, err := errors.Marshal(errors.SabredavMethodNotAllowed, "The resource you tried to create already exists", "") + b, err := errors.Marshal(http.StatusMethodNotAllowed, "The resource you tried to create already exists", "") errors.HandleWebdavError(&log, w, b, err) } else { errors.HandleErrorStatus(&log, w, statRes.Status) @@ -204,7 +204,7 @@ func (s *svc) handleMkcol(ctx context.Context, w http.ResponseWriter, r *http.Re w.WriteHeader(http.StatusForbidden) // TODO path could be empty or relative... m := fmt.Sprintf("Permission denied to create %v", childRef.Path) - b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + b, err := errors.Marshal(http.StatusForbidden, m, "") errors.HandleWebdavError(&log, w, b, err) default: errors.HandleErrorStatus(&log, w, res.Status) diff --git a/internal/http/services/owncloud/ocdav/move.go b/internal/http/services/owncloud/ocdav/move.go index d68ffff23e8..8947ff8bf9a 100644 --- a/internal/http/services/owncloud/ocdav/move.go +++ b/internal/http/services/owncloud/ocdav/move.go @@ -177,7 +177,7 @@ func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Req if srcStatRes.Status.Code == rpc.Code_CODE_NOT_FOUND { w.WriteHeader(http.StatusNotFound) m := fmt.Sprintf("Resource %v not found", srcStatReq.Ref.Path) - b, err := errors.Marshal(errors.SabredavNotFound, m, "") + b, err := errors.Marshal(http.StatusNotFound, m, "") errors.HandleWebdavError(&log, w, b, err) } errors.HandleErrorStatus(&log, w, srcStatRes.Status) @@ -257,7 +257,7 @@ func (s *svc) handleMove(ctx context.Context, w http.ResponseWriter, r *http.Req if mRes.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { w.WriteHeader(http.StatusForbidden) m := fmt.Sprintf("Permission denied to move %v", src.Path) - b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + b, err := errors.Marshal(http.StatusForbidden, m, "") errors.HandleWebdavError(&log, w, b, err) } errors.HandleErrorStatus(&log, w, mRes.Status) diff --git a/internal/http/services/owncloud/ocdav/net/headers.go b/internal/http/services/owncloud/ocdav/net/headers.go index 657ad770202..8e5798ff92c 100644 --- a/internal/http/services/owncloud/ocdav/net/headers.go +++ b/internal/http/services/owncloud/ocdav/net/headers.go @@ -34,25 +34,33 @@ const ( HeaderIfMatch = "If-Match" ) +// webdav headers +const ( + HeaderDav = "DAV" // https://datatracker.ietf.org/doc/html/rfc4918#section-10.1 + HeaderDepth = "Depth" // https://datatracker.ietf.org/doc/html/rfc4918#section-10.2 + HeaderDestination = "Destination" // https://datatracker.ietf.org/doc/html/rfc4918#section-10.3 + HeaderIf = "If" // https://datatracker.ietf.org/doc/html/rfc4918#section-10.4 + HeaderLockToken = "Lock-Token" // https://datatracker.ietf.org/doc/html/rfc4918#section-10.5 + HeaderOverwrite = "Overwrite" // https://datatracker.ietf.org/doc/html/rfc4918#section-10.6 + HeaderTimeout = "Timeout" // https://datatracker.ietf.org/doc/html/rfc4918#section-10.7 +) + // Non standard HTTP headers. const ( HeaderOCFileID = "OC-FileId" HeaderOCETag = "OC-ETag" HeaderOCChecksum = "OC-Checksum" HeaderOCPermissions = "OC-Perm" - HeaderDepth = "Depth" - HeaderDav = "DAV" HeaderTusResumable = "Tus-Resumable" HeaderTusVersion = "Tus-Version" HeaderTusExtension = "Tus-Extension" HeaderTusChecksumAlgorithm = "Tus-Checksum-Algorithm" HeaderTusUploadExpires = "Upload-Expires" - HeaderDestination = "Destination" - HeaderOverwrite = "Overwrite" HeaderUploadChecksum = "Upload-Checksum" HeaderUploadLength = "Upload-Length" HeaderUploadMetadata = "Upload-Metadata" HeaderUploadOffset = "Upload-Offset" HeaderOCMtime = "X-OC-Mtime" HeaderExpectedEntityLength = "X-Expected-Entity-Length" + HeaderLitmus = "X-Litmus" ) diff --git a/internal/http/services/owncloud/ocdav/ocdav.go b/internal/http/services/owncloud/ocdav/ocdav.go index ae2e5ac570c..fd1303e1a11 100644 --- a/internal/http/services/owncloud/ocdav/ocdav.go +++ b/internal/http/services/owncloud/ocdav/ocdav.go @@ -114,6 +114,8 @@ type svc struct { davHandler *DavHandler favoritesManager favorite.Manager client *http.Client + // LockSystem is the lock management system. + LockSystem LockSystem } func (s *svc) Config() *Config { @@ -126,6 +128,14 @@ func getFavoritesManager(c *Config) (favorite.Manager, error) { } return nil, errtypes.NotFound("driver not found: " + c.FavoriteStorageDriver) } +func getLockSystem(c *Config) (LockSystem, error) { + // TODO in memory implementation + client, err := pool.GetGatewayServiceClient(c.GatewaySvc) + if err != nil { + return nil, err + } + return NewCS3LS(client), nil +} // New returns a new ocdav func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error) { @@ -140,6 +150,10 @@ func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error) if err != nil { return nil, err } + ls, err := getLockSystem(conf) + if err != nil { + return nil, err + } s := &svc{ c: conf, @@ -150,6 +164,7 @@ func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error) rhttp.Insecure(conf.Insecure), ), favoritesManager: fm, + LockSystem: ls, } // initialize handlers and set default configs if err := s.webDavHandler.init(conf.WebdavNamespace, true); err != nil { @@ -182,7 +197,7 @@ func (s *svc) Handler() http.Handler { // TODO(jfd): do we need this? // fake litmus testing for empty namespace: see https://github.com/golang/net/blob/e514e69ffb8bc3c76a71ae40de0118d794855992/webdav/litmus_test_server.go#L58-L89 - if r.Header.Get("X-Litmus") == "props: 3 (propfind_invalid2)" { + if r.Header.Get(net.HeaderLitmus) == "props: 3 (propfind_invalid2)" { http.Error(w, "400 Bad Request", http.StatusBadRequest) return } diff --git a/internal/http/services/owncloud/ocdav/propfind/propfind.go b/internal/http/services/owncloud/ocdav/propfind/propfind.go index 874ba81e80d..b1aadcf8ee8 100644 --- a/internal/http/services/owncloud/ocdav/propfind/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind/propfind.go @@ -244,7 +244,7 @@ func (p *Handler) getResourceInfos(ctx context.Context, w http.ResponseWriter, r log.Debug().Str("depth", dh).Msg(err.Error()) w.WriteHeader(http.StatusBadRequest) m := fmt.Sprintf("Invalid Depth header value: %v", dh) - b, err := errors.Marshal(errors.SabredavBadRequest, m, "") + b, err := errors.Marshal(http.StatusBadRequest, m, "") errors.HandleWebdavError(&log, w, b, err) return nil, false, false } @@ -326,7 +326,7 @@ func (p *Handler) getResourceInfos(ctx context.Context, w http.ResponseWriter, r // TODO if we have children invent node on the fly w.WriteHeader(http.StatusNotFound) m := fmt.Sprintf("Resource %v not found", requestPath) - b, err := errors.Marshal(errors.SabredavNotFound, m, "") + b, err := errors.Marshal(http.StatusNotFound, m, "") errors.HandleWebdavError(&log, w, b, err) return nil, false, false } @@ -348,6 +348,12 @@ func (p *Handler) getResourceInfos(ctx context.Context, w http.ResponseWriter, r resourceInfos := []*provider.ResourceInfo{ rootInfo, // PROPFIND always includes the root resource } + + if rootInfo.Type == provider.ResourceType_RESOURCE_TYPE_FILE { + // no need to stat any other spaces, we got our file stat already + return resourceInfos, true, true + } + childInfos := map[string]*provider.ResourceInfo{} // then add children @@ -524,19 +530,19 @@ func ReadPropfind(r io.Reader) (pf XML, status int, err error) { // http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND return XML{Allprop: new(struct{})}, 0, nil } - err = errors.ErrorInvalidPropfind + err = errors.ErrInvalidPropfind } return XML{}, http.StatusBadRequest, err } if pf.Allprop == nil && pf.Include != nil { - return XML{}, http.StatusBadRequest, errors.ErrorInvalidPropfind + return XML{}, http.StatusBadRequest, errors.ErrInvalidPropfind } if pf.Allprop != nil && (pf.Prop != nil || pf.Propname != nil) { - return XML{}, http.StatusBadRequest, errors.ErrorInvalidPropfind + return XML{}, http.StatusBadRequest, errors.ErrInvalidPropfind } if pf.Prop != nil && pf.Propname != nil { - return XML{}, http.StatusBadRequest, errors.ErrorInvalidPropfind + return XML{}, http.StatusBadRequest, errors.ErrInvalidPropfind } if pf.Propname == nil && pf.Allprop == nil && pf.Prop == nil { // jfd: I think is perfectly valid ... treat it as allprop @@ -594,6 +600,7 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p // -3 indicates unlimited quota := net.PropQuotaUnknown size := strconv.FormatUint(md.Size, 10) + var lock *provider.Lock // TODO refactor helper functions: GetOpaqueJSONEncoded(opaque, key string, *struct) err, GetOpaquePlainEncoded(opaque, key) value, err // or use ok like pattern and return bool? if md.Opaque != nil && md.Opaque.Map != nil { @@ -607,6 +614,13 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p if md.Opaque.Map["quota"] != nil && md.Opaque.Map["quota"].Decoder == "plain" { quota = string(md.Opaque.Map["quota"].Value) } + if md.Opaque.Map["lock"] != nil && md.Opaque.Map["lock"].Decoder == "json" { + lock = &provider.Lock{} + err := json.Unmarshal(md.Opaque.Map["lock"].Value, lock) + if err != nil { + sublog.Error().Err(err).Msg("could not unmarshal locks json") + } + } } role := conversions.RoleFromResourcePermissions(md.PermissionSet) @@ -725,6 +739,10 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p propstatOK.Prop = append(propstatOK.Prop, props.NewProp("oc:favorite", "0")) } } + + if lock != nil { + propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("d:lockdiscovery", activeLocks(&sublog, lock))) + } // TODO return other properties ... but how do we put them in a namespace? } else { // otherwise return only the requested properties @@ -1026,6 +1044,12 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p } else { propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:quota-available-bytes", "")) } + case "lockdiscovery": // http://www.webdav.org/specs/rfc2518.html#PROPERTY_lockdiscovery + if lock == nil { + propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:lockdiscovery", "")) + } else { + propstatOK.Prop = append(propstatOK.Prop, props.NewPropRaw("d:lockdiscovery", activeLocks(&sublog, lock))) + } default: propstatNotFound.Prop = append(propstatNotFound.Prop, props.NewProp("d:"+pf.Prop[i].Local, "")) } @@ -1076,6 +1100,78 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p return &response, nil } +func activeLocks(log *zerolog.Logger, lock *provider.Lock) string { + if lock == nil || lock.Type == provider.LockType_LOCK_TYPE_INVALID { + return "" + } + expiration := "Infinity" + if lock.Expiration != nil { + now := uint64(time.Now().Unix()) + // Should we hide expired locks here? No. + // + // If the timeout expires, then the lock SHOULD be removed. In this + // case the server SHOULD act as if an UNLOCK method was executed by the + // server on the resource using the lock token of the timed-out lock, + // performed with its override authority. + // + // see https://datatracker.ietf.org/doc/html/rfc4918#section-6.6 + if lock.Expiration.Seconds >= now { + expiration = "Second-" + strconv.FormatUint(lock.Expiration.Seconds-now, 10) + } else { + expiration = "Second-0" + } + } + + // xml.Encode cannot render emptytags like , see https://github.com/golang/go/issues/21399 + var activelocks strings.Builder + activelocks.WriteString("") + // webdav locktype write | transaction + switch lock.Type { + case provider.LockType_LOCK_TYPE_EXCL: + fallthrough + case provider.LockType_LOCK_TYPE_WRITE: + activelocks.WriteString("") + } + // webdav lockscope exclusive, shared, or local + switch lock.Type { + case provider.LockType_LOCK_TYPE_EXCL: + fallthrough + case provider.LockType_LOCK_TYPE_WRITE: + activelocks.WriteString("") + case provider.LockType_LOCK_TYPE_SHARED: + activelocks.WriteString("") + } + // we currently only support depth infinity + activelocks.WriteString("Infinity") + + if lock.User != nil || lock.AppName != "" { + activelocks.WriteString("") + + if lock.User != nil { + // TODO oc10 uses displayname and email, needs a user lookup + activelocks.WriteString(props.Escape(lock.User.OpaqueId + "@" + lock.User.Idp)) + } + if lock.AppName != "" { + if lock.User != nil { + activelocks.WriteString(" via ") + } + activelocks.WriteString(props.Escape(lock.AppName)) + } + activelocks.WriteString("") + } + activelocks.WriteString("") + activelocks.WriteString(expiration) + activelocks.WriteString("") + if lock.LockId != "" { + activelocks.WriteString("") + activelocks.WriteString(props.Escape(lock.LockId)) + activelocks.WriteString("") + } + // lockroot is only used when setting the lock + activelocks.WriteString("") + return activelocks.String() +} + // be defensive about wrong encoded etags func quoteEtag(etag string) string { if strings.HasPrefix(etag, "W/") { diff --git a/internal/http/services/owncloud/ocdav/proppatch.go b/internal/http/services/owncloud/ocdav/proppatch.go index 9724a26a47b..696331cfc16 100644 --- a/internal/http/services/owncloud/ocdav/proppatch.go +++ b/internal/http/services/owncloud/ocdav/proppatch.go @@ -54,7 +54,7 @@ func (s *svc) handlePathProppatch(w http.ResponseWriter, r *http.Request, ns str sublog.Debug().Err(err).Msg("error reading proppatch") w.WriteHeader(status) m := fmt.Sprintf("Error reading proppatch: %v", err) - b, err := errors.Marshal(errors.SabredavBadRequest, m, "") + b, err := errors.Marshal(status, m, "") errors.HandleWebdavError(&sublog, w, b, err) return } @@ -89,7 +89,7 @@ func (s *svc) handlePathProppatch(w http.ResponseWriter, r *http.Request, ns str if statRes.Status.Code == rpc.Code_CODE_NOT_FOUND { w.WriteHeader(http.StatusNotFound) m := fmt.Sprintf("Resource %v not found", fn) - b, err := errors.Marshal(errors.SabredavNotFound, m, "") + b, err := errors.Marshal(http.StatusNotFound, m, "") errors.HandleWebdavError(&sublog, w, b, err) } errors.HandleErrorStatus(&sublog, w, statRes.Status) @@ -232,7 +232,7 @@ func (s *svc) handleProppatch(ctx context.Context, w http.ResponseWriter, r *htt if res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { w.WriteHeader(http.StatusForbidden) m := fmt.Sprintf("Permission denied to remove properties on resource %v", ref.Path) - b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + b, err := errors.Marshal(http.StatusForbidden, m, "") errors.HandleWebdavError(&log, w, b, err) return nil, nil, false } @@ -266,7 +266,7 @@ func (s *svc) handleProppatch(ctx context.Context, w http.ResponseWriter, r *htt if res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { w.WriteHeader(http.StatusForbidden) m := fmt.Sprintf("Permission denied to set properties on resource %v", ref.Path) - b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + b, err := errors.Marshal(http.StatusForbidden, m, "") errors.HandleWebdavError(&log, w, b, err) return nil, nil, false } diff --git a/internal/http/services/owncloud/ocdav/props/props.go b/internal/http/services/owncloud/ocdav/props/props.go index 54142cbd979..f3065e357ce 100644 --- a/internal/http/services/owncloud/ocdav/props/props.go +++ b/internal/http/services/owncloud/ocdav/props/props.go @@ -97,3 +97,31 @@ func Next(d *xml.Decoder) (xml.Token, error) { } } } + +// ActiveLock holds active lock xml data +// http://www.webdav.org/specs/rfc4918.html#ELEMENT_activelock +// +type ActiveLock struct { + XMLName xml.Name `xml:"activelock"` + Exclusive *struct{} `xml:"lockscope>exclusive,omitempty"` + Shared *struct{} `xml:"lockscope>shared,omitempty"` + Write *struct{} `xml:"locktype>write,omitempty"` + Depth string `xml:"depth"` + Owner Owner `xml:"owner,omitempty"` + Timeout string `xml:"timeout,omitempty"` + Locktoken string `xml:"locktoken>href"` + Lockroot string `xml:"lockroot>href,omitempty"` +} + +// Owner captures the inner UML of a lock owner element http://www.webdav.org/specs/rfc4918.html#ELEMENT_owner +type Owner struct { + InnerXML string `xml:",innerxml"` +} + +// Escape repaces ", &, ', < and > with their xml representation +func Escape(s string) string { + b := bytes.NewBuffer(nil) + _ = xml.EscapeText(b, []byte(s)) + return b.String() +} diff --git a/internal/http/services/owncloud/ocdav/put.go b/internal/http/services/owncloud/ocdav/put.go index aa3d2c2f2b0..481f4fe27ea 100644 --- a/internal/http/services/owncloud/ocdav/put.go +++ b/internal/http/services/owncloud/ocdav/put.go @@ -248,7 +248,7 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ switch uRes.Status.Code { case rpc.Code_CODE_PERMISSION_DENIED: w.WriteHeader(http.StatusForbidden) - b, err := errors.Marshal(errors.SabredavPermissionDenied, "permission denied: you have no permission to upload content", "") + b, err := errors.Marshal(http.StatusForbidden, "permission denied: you have no permission to upload content", "") errors.HandleWebdavError(&log, w, b, err) case rpc.Code_CODE_NOT_FOUND: w.WriteHeader(http.StatusConflict) @@ -289,7 +289,7 @@ func (s *svc) handlePut(ctx context.Context, w http.ResponseWriter, r *http.Requ } if httpRes.StatusCode == errtypes.StatusChecksumMismatch { w.WriteHeader(http.StatusBadRequest) - b, err := errors.Marshal(errors.SabredavBadRequest, "The computed checksum does not match the one received from the client.", "") + b, err := errors.Marshal(http.StatusBadRequest, "The computed checksum does not match the one received from the client.", "") errors.HandleWebdavError(&log, w, b, err) return } diff --git a/internal/http/services/owncloud/ocdav/spacelookup/spacelookup.go b/internal/http/services/owncloud/ocdav/spacelookup/spacelookup.go index b97b18aee02..9f444505098 100644 --- a/internal/http/services/owncloud/ocdav/spacelookup/spacelookup.go +++ b/internal/http/services/owncloud/ocdav/spacelookup/spacelookup.go @@ -32,8 +32,23 @@ import ( "github.com/cs3org/reva/pkg/utils" ) +// LookupReferenceForPath returns: +// a reference with root and relative path +// the status and error for the lookup +func LookupReferenceForPath(ctx context.Context, client gateway.GatewayAPIClient, path string) (*storageProvider.Reference, *rpc.Status, error) { + space, cs3Status, err := LookUpStorageSpaceForPath(ctx, client, path) + if err != nil || cs3Status.Code != rpc.Code_CODE_OK { + return nil, cs3Status, err + } + spacePath := string(space.Opaque.Map["path"].Value) // FIXME error checks + return &storageProvider.Reference{ + ResourceId: space.Root, + Path: utils.MakeRelativePath(strings.TrimPrefix(path, spacePath)), + }, cs3Status, nil +} + // LookUpStorageSpaceForPath returns: -// th storage spaces responsible for a path +// the storage spaces responsible for a path // the status and error for the lookup func LookUpStorageSpaceForPath(ctx context.Context, client gateway.GatewayAPIClient, path string) (*storageProvider.StorageSpace, *rpc.Status, error) { // TODO add filter to only fetch spaces changed in the last 30 sec? diff --git a/internal/http/services/owncloud/ocdav/spaces.go b/internal/http/services/owncloud/ocdav/spaces.go index e633dc84771..21036c0b1f8 100644 --- a/internal/http/services/owncloud/ocdav/spaces.go +++ b/internal/http/services/owncloud/ocdav/spaces.go @@ -22,7 +22,9 @@ import ( "net/http" "path" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/errors" "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/propfind" + "github.com/cs3org/reva/pkg/appctx" "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/pkg/rhttp/router" ) @@ -71,9 +73,43 @@ func (h *SpacesHandler) Handler(s *svc) http.Handler { case MethodProppatch: s.handleSpacesProppatch(w, r, spaceID) case MethodLock: - s.handleLock(w, r, spaceID) + log := appctx.GetLogger(r.Context()) + // TODO initialize status with http.StatusBadRequest + // TODO initialize err with errors.ErrUnsupportedMethod + status, err := s.handleSpacesLock(w, r, spaceID) + if err != nil { + log.Error().Err(err).Str("space", spaceID).Msg(err.Error()) + } + if status != 0 { // 0 would mean handleLock already sent the response + w.WriteHeader(status) + if status != http.StatusNoContent { + var b []byte + if b, err = errors.Marshal(status, err.Error(), ""); err == nil { + _, err = w.Write(b) + } + if err != nil { + log.Error().Err(err).Str("space", spaceID).Msg(err.Error()) + } + } + } case MethodUnlock: - s.handleUnlock(w, r, spaceID) + log := appctx.GetLogger(r.Context()) + status, err := s.handleUnlock(w, r, spaceID) + if err != nil { + log.Error().Err(err).Str("space", spaceID).Msg(err.Error()) + } + if status != 0 { // 0 would mean handleUnlock already sent the response + w.WriteHeader(status) + if status != http.StatusNoContent { + var b []byte + if b, err = errors.Marshal(status, err.Error(), ""); err == nil { + _, err = w.Write(b) + } + if err != nil { + log.Error().Err(err).Str("space", spaceID).Msg(err.Error()) + } + } + } case MethodMkcol: s.handleSpacesMkCol(w, r, spaceID) case MethodMove: diff --git a/internal/http/services/owncloud/ocdav/trashbin.go b/internal/http/services/owncloud/ocdav/trashbin.go index 6bf159160e3..2e28a9be1a9 100644 --- a/internal/http/services/owncloud/ocdav/trashbin.go +++ b/internal/http/services/owncloud/ocdav/trashbin.go @@ -86,13 +86,13 @@ func (h *TrashbinHandler) Handler(s *svc) http.Handler { if u.Username != username { log.Debug().Str("username", username).Interface("user", u).Msg("trying to read another users trash") // listing other users trash is forbidden, no auth will change that - b, err := errors.Marshal(errors.SabredavNotAuthenticated, "", "") + w.WriteHeader(http.StatusUnauthorized) + b, err := errors.Marshal(http.StatusUnauthorized, "", "") if err != nil { log.Error().Msgf("error marshaling xml response: %s", b) w.WriteHeader(http.StatusInternalServerError) return } - w.WriteHeader(http.StatusUnauthorized) _, err = w.Write(b) if err != nil { log.Error().Msgf("error writing xml response: %s", b) @@ -533,7 +533,8 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc } if parentStatResponse.Status.Code == rpc.Code_CODE_NOT_FOUND { - errors.HandleErrorStatus(&sublog, w, &rpc.Status{Code: rpc.Code_CODE_FAILED_PRECONDITION}) + // 409 if intermediate dir is missing, see https://tools.ietf.org/html/rfc4918#section-9.8.5 + w.WriteHeader(http.StatusConflict) return } } @@ -546,7 +547,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc sublog.Warn().Str("overwrite", overwrite).Msg("dst already exists") w.WriteHeader(http.StatusPreconditionFailed) // 412, see https://tools.ietf.org/html/rfc4918#section-9.9.4 b, err := errors.Marshal( - errors.SabredavPreconditionFailed, + http.StatusPreconditionFailed, "The destination node already exists, and the overwrite header is set to false", net.HeaderOverwrite, ) @@ -598,7 +599,7 @@ func (h *TrashbinHandler) restore(w http.ResponseWriter, r *http.Request, s *svc if res.Status.Code != rpc.Code_CODE_OK { if res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED { w.WriteHeader(http.StatusForbidden) - b, err := errors.Marshal(errors.SabredavPermissionDenied, "Permission denied to restore", "") + b, err := errors.Marshal(http.StatusForbidden, "Permission denied to restore", "") errors.HandleWebdavError(&sublog, w, b, err) } errors.HandleErrorStatus(&sublog, w, res.Status) @@ -670,7 +671,7 @@ func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, sublog.Debug().Str("path", basePath).Str("key", key).Interface("status", res.Status).Msg("resource not found") w.WriteHeader(http.StatusConflict) m := fmt.Sprintf("path %s not found", basePath) - b, err := errors.Marshal(errors.SabredavConflict, m, "") + b, err := errors.Marshal(http.StatusConflict, m, "") errors.HandleWebdavError(&sublog, w, b, err) case rpc.Code_CODE_PERMISSION_DENIED: w.WriteHeader(http.StatusForbidden) @@ -680,7 +681,7 @@ func (h *TrashbinHandler) delete(w http.ResponseWriter, r *http.Request, s *svc, } else { m = "Permission denied to delete" } - b, err := errors.Marshal(errors.SabredavPermissionDenied, m, "") + b, err := errors.Marshal(http.StatusForbidden, m, "") errors.HandleWebdavError(&sublog, w, b, err) default: errors.HandleErrorStatus(&sublog, w, res.Status) diff --git a/internal/http/services/owncloud/ocdav/tus.go b/internal/http/services/owncloud/ocdav/tus.go index 7d48af06d02..104429b926a 100644 --- a/internal/http/services/owncloud/ocdav/tus.go +++ b/internal/http/services/owncloud/ocdav/tus.go @@ -121,7 +121,7 @@ func (s *svc) handleTusPost(ctx context.Context, w http.ResponseWriter, r *http. w.WriteHeader(http.StatusPreconditionFailed) return } - // r.Header.Get("OC-Checksum") + // r.Header.Get(net.HeaderOCChecksum) // TODO must be SHA1, ADLER32 or MD5 ... in capital letters???? // curl -X PUT https://demo.owncloud.com/remote.php/webdav/testcs.bin -u demo:demo -d '123' -v -H 'OC-Checksum: SHA1:40bd001563085fc35165329ea1ff5c5ecbdbbeef' diff --git a/internal/http/services/owncloud/ocdav/versions.go b/internal/http/services/owncloud/ocdav/versions.go index 9a2d756d310..2bb53916837 100644 --- a/internal/http/services/owncloud/ocdav/versions.go +++ b/internal/http/services/owncloud/ocdav/versions.go @@ -113,7 +113,7 @@ func (h *VersionsHandler) doListVersions(w http.ResponseWriter, r *http.Request, if res.Status.Code != rpc.Code_CODE_OK { if res.Status.Code == rpc.Code_CODE_PERMISSION_DENIED || res.Status.Code == rpc.Code_CODE_NOT_FOUND { w.WriteHeader(http.StatusNotFound) - b, err := errors.Marshal(errors.SabredavNotFound, "Resource not found", "") + b, err := errors.Marshal(http.StatusNotFound, "Resource not found", "") errors.HandleWebdavError(&sublog, w, b, err) return } diff --git a/internal/http/services/owncloud/ocdav/webdav.go b/internal/http/services/owncloud/ocdav/webdav.go index fcb24b6a175..7b8c15b57fc 100644 --- a/internal/http/services/owncloud/ocdav/webdav.go +++ b/internal/http/services/owncloud/ocdav/webdav.go @@ -62,10 +62,11 @@ func (h *WebDavHandler) Handler(s *svc) http.Handler { ns, newPath, err := s.ApplyLayout(r.Context(), h.namespace, h.useLoggedInUserNS, r.URL.Path) if err != nil { w.WriteHeader(http.StatusNotFound) - b, err := errors.Marshal(errors.SabredavNotFound, fmt.Sprintf("could not get storage for %s", r.URL.Path), "") + b, err := errors.Marshal(http.StatusNotFound, fmt.Sprintf("could not get storage for %s", r.URL.Path), "") errors.HandleWebdavError(appctx.GetLogger(r.Context()), w, b, err) } r.URL.Path = newPath + switch r.Method { case MethodPropfind: p := propfind.NewHandler(config.PublicURL, func() (propfind.GatewayClient, error) { @@ -73,9 +74,37 @@ func (h *WebDavHandler) Handler(s *svc) http.Handler { }) p.HandlePathPropfind(w, r, ns) case MethodLock: - s.handleLock(w, r, ns) + log := appctx.GetLogger(r.Context()) + // TODO initialize status with http.StatusBadRequest + // TODO initialize err with errors.ErrUnsupportedMethod + status, err := s.handleLock(w, r, ns) + if status != 0 { // 0 would mean handleLock already sent the response + w.WriteHeader(status) + if status != http.StatusNoContent { + var b []byte + if b, err = errors.Marshal(status, err.Error(), ""); err == nil { + _, err = w.Write(b) + } + } + } + if err != nil { + log.Error().Err(err).Msg(err.Error()) + } case MethodUnlock: - s.handleUnlock(w, r, ns) + log := appctx.GetLogger(r.Context()) + status, err := s.handleUnlock(w, r, ns) + if status != 0 { // 0 would mean handleUnlock already sent the response + w.WriteHeader(status) + if status != http.StatusNoContent { + var b []byte + if b, err = errors.Marshal(status, err.Error(), ""); err == nil { + _, err = w.Write(b) + } + } + } + if err != nil { + log.Error().Err(err).Msg(err.Error()) + } case MethodProppatch: s.handlePathProppatch(w, r, ns) case MethodMkcol: diff --git a/internal/http/services/owncloud/ocdav/unlock.go b/pkg/ctx/lockctx.go similarity index 67% rename from internal/http/services/owncloud/ocdav/unlock.go rename to pkg/ctx/lockctx.go index 3b20189c38a..329b41ee17e 100644 --- a/internal/http/services/owncloud/ocdav/unlock.go +++ b/pkg/ctx/lockctx.go @@ -16,13 +16,19 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -package ocdav +package ctx import ( - "net/http" + "context" ) -// TODO(jfd): implement unlock -func (s *svc) handleUnlock(w http.ResponseWriter, r *http.Request, ns string) { - w.WriteHeader(http.StatusNotImplemented) +// ContextGetLockID returns the lock id if set in the given context. +func ContextGetLockID(ctx context.Context) (string, bool) { + u, ok := ctx.Value(lockIDKey).(string) + return u, ok +} + +// ContextSetLockID stores the lock id in the context. +func ContextSetLockID(ctx context.Context, t string) context.Context { + return context.WithValue(ctx, lockIDKey, t) } diff --git a/pkg/ctx/userctx.go b/pkg/ctx/userctx.go index f433481f87f..3c50df1879a 100644 --- a/pkg/ctx/userctx.go +++ b/pkg/ctx/userctx.go @@ -30,6 +30,7 @@ const ( userKey key = iota tokenKey idKey + lockIDKey ) // ContextGetUser returns the user if set in the given context. diff --git a/pkg/errtypes/errtypes.go b/pkg/errtypes/errtypes.go index 4f5ced408f8..14d18f744c5 100644 --- a/pkg/errtypes/errtypes.go +++ b/pkg/errtypes/errtypes.go @@ -23,6 +23,8 @@ package errtypes import ( + "strings" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" ) @@ -50,6 +52,27 @@ func (e PermissionDenied) Error() string { return "error: permission denied: " + // IsPermissionDenied implements the IsPermissionDenied interface. func (e PermissionDenied) IsPermissionDenied() {} +// Locked is the error to use when a resource cannot be modified because of a lock. +type Locked string + +func (e Locked) Error() string { return "error: locked by " + string(e) } + +// LockID returns the lock ID that caused this error +func (e Locked) LockID() string { + return string(e) +} + +// IsLocked implements the IsLocked interface. +func (e Locked) IsLocked() {} + +// PreconditionFailed is the error to use when a request fails because a requested etag or lock ID mismatches. +type PreconditionFailed string + +func (e PreconditionFailed) Error() string { return "error: precondition failed: " + string(e) } + +// IsPreconditionFailed implements the IsPreconditionFailed interface. +func (e PreconditionFailed) IsPreconditionFailed() {} + // AlreadyExists is the error to use when a resource something is not found. type AlreadyExists string @@ -168,6 +191,18 @@ type IsPermissionDenied interface { IsPermissionDenied() } +// IsLocked is the interface to implement +// to specify that an resource is locked. +type IsLocked interface { + IsLocked() +} + +// IsPreconditionFailed is the interface to implement +// to specify that a precondition failed. +type IsPreconditionFailed interface { + IsPreconditionFailed() +} + // IsPartialContent is the interface to implement // to specify that the client request has partial data. type IsPartialContent interface { @@ -208,14 +243,20 @@ func NewErrtypeFromStatus(status *rpc.Status) error { case rpc.Code_CODE_UNIMPLEMENTED: return NotSupported(status.Message) case rpc.Code_CODE_PERMISSION_DENIED: + // FIXME add locked status! + if strings.HasPrefix(status.Message, "set lock: error: locked by ") { + return Locked(strings.TrimPrefix(status.Message, "set lock: error: locked by ")) + } return PermissionDenied(status.Message) - // case rpc.Code_CODE_DATA_LOSS: ? - // IsPartialContent - // case rpc.Code_CODE_FAILED_PRECONDITION: ? - // IsChecksumMismatch + // case rpc.Code_CODE_LOCKED: + // return Locked(status.Message) + // case rpc.Code_CODE_DATA_LOSS: ? + // IsPartialContent + case rpc.Code_CODE_FAILED_PRECONDITION: + return PreconditionFailed(status.Message) case rpc.Code_CODE_INSUFFICIENT_STORAGE: return InsufficientStorage(status.Message) - case rpc.Code_CODE_INVALID_ARGUMENT, rpc.Code_CODE_FAILED_PRECONDITION, rpc.Code_CODE_OUT_OF_RANGE: + case rpc.Code_CODE_INVALID_ARGUMENT, rpc.Code_CODE_OUT_OF_RANGE: return BadRequest(status.Message) default: return InternalError(status.Message) diff --git a/pkg/rgrpc/status/status.go b/pkg/rgrpc/status/status.go index e10d85c3497..9c25f5563c3 100644 --- a/pkg/rgrpc/status/status.go +++ b/pkg/rgrpc/status/status.go @@ -24,6 +24,7 @@ package status import ( "context" "errors" + "net/http" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" "github.com/cs3org/reva/pkg/errtypes" @@ -85,6 +86,15 @@ func NewPermissionDenied(ctx context.Context, err error, msg string) *rpc.Status } } +// NewFailedPrecondition returns a Status with FAILED_PRECONDITION. +func NewFailedPrecondition(ctx context.Context, err error, msg string) *rpc.Status { + return &rpc.Status{ + Code: rpc.Code_CODE_FAILED_PRECONDITION, + Message: msg, + Trace: getTrace(ctx), + } +} + // NewInsufficientStorage returns a Status with INSUFFICIENT_STORAGE. func NewInsufficientStorage(ctx context.Context, err error, msg string) *rpc.Status { return &rpc.Status{ @@ -133,14 +143,21 @@ func NewConflict(ctx context.Context, err error, msg string) *rpc.Status { func NewStatusFromErrType(ctx context.Context, msg string, err error) *rpc.Status { switch e := err.(type) { case nil: - NewOK(ctx) + return NewOK(ctx) case errtypes.IsNotFound: return NewNotFound(ctx, msg+": "+err.Error()) + case errtypes.AlreadyExists: + return NewAlreadyExists(ctx, err, msg+": "+err.Error()) case errtypes.IsInvalidCredentials: // TODO this maps badly return NewUnauthenticated(ctx, err, msg+": "+err.Error()) case errtypes.PermissionDenied: return NewPermissionDenied(ctx, e, msg+": "+err.Error()) + case errtypes.Locked: + // FIXME a locked error returns the current lockid + return NewPermissionDenied(ctx, e, msg+": "+err.Error()) + case errtypes.PreconditionFailed: + return NewFailedPrecondition(ctx, e, msg+": "+err.Error()) case errtypes.IsNotSupported: return NewUnimplemented(ctx, err, msg+":"+err.Error()) case errtypes.BadRequest: @@ -183,3 +200,36 @@ func getTrace(ctx context.Context) string { span := trace.SpanFromContext(ctx) return span.SpanContext().TraceID().String() } + +// a mapping from the CS3 status codes to http codes +var httpStatusCode = map[rpc.Code]int{ + rpc.Code_CODE_ABORTED: http.StatusConflict, + rpc.Code_CODE_ALREADY_EXISTS: http.StatusConflict, + rpc.Code_CODE_CANCELLED: 499, // Client Closed Request + rpc.Code_CODE_DATA_LOSS: http.StatusInternalServerError, + rpc.Code_CODE_DEADLINE_EXCEEDED: http.StatusGatewayTimeout, + rpc.Code_CODE_FAILED_PRECONDITION: http.StatusPreconditionFailed, + rpc.Code_CODE_INSUFFICIENT_STORAGE: http.StatusInsufficientStorage, + rpc.Code_CODE_INTERNAL: http.StatusInternalServerError, + rpc.Code_CODE_INVALID: http.StatusInternalServerError, + rpc.Code_CODE_INVALID_ARGUMENT: http.StatusBadRequest, + rpc.Code_CODE_NOT_FOUND: http.StatusNotFound, + rpc.Code_CODE_OK: http.StatusOK, + rpc.Code_CODE_OUT_OF_RANGE: http.StatusBadRequest, + rpc.Code_CODE_PERMISSION_DENIED: http.StatusForbidden, + rpc.Code_CODE_REDIRECTION: http.StatusTemporaryRedirect, // or permanent? + rpc.Code_CODE_RESOURCE_EXHAUSTED: http.StatusTooManyRequests, + rpc.Code_CODE_UNAUTHENTICATED: http.StatusUnauthorized, + rpc.Code_CODE_UNAVAILABLE: http.StatusServiceUnavailable, + rpc.Code_CODE_UNIMPLEMENTED: http.StatusNotImplemented, + rpc.Code_CODE_UNKNOWN: http.StatusInternalServerError, +} + +// HTTPStatusFromCode returns an HTTP status code for the rpc code. It returns +// an internal server error (500) if the code is unknown +func HTTPStatusFromCode(code rpc.Code) int { + if s, ok := httpStatusCode[code]; ok { + return s + } + return http.StatusInternalServerError +} diff --git a/pkg/storage/fs/nextcloud/nextcloud.go b/pkg/storage/fs/nextcloud/nextcloud.go index 1750cd968c8..8f38342de14 100644 --- a/pkg/storage/fs/nextcloud/nextcloud.go +++ b/pkg/storage/fs/nextcloud/nextcloud.go @@ -786,7 +786,7 @@ func (nc *StorageDriver) RefreshLock(ctx context.Context, ref *provider.Referenc } // Unlock removes an existing lock from the given reference -func (nc *StorageDriver) Unlock(ctx context.Context, ref *provider.Reference) error { +func (nc *StorageDriver) Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { return errtypes.NotSupported("unimplemented") } diff --git a/pkg/storage/fs/owncloudsql/owncloudsql.go b/pkg/storage/fs/owncloudsql/owncloudsql.go index d0fe1d39cd9..c6a9024a1f5 100644 --- a/pkg/storage/fs/owncloudsql/owncloudsql.go +++ b/pkg/storage/fs/owncloudsql/owncloudsql.go @@ -1038,7 +1038,7 @@ func (fs *owncloudsqlfs) RefreshLock(ctx context.Context, ref *provider.Referenc } // Unlock removes an existing lock from the given reference -func (fs *owncloudsqlfs) Unlock(ctx context.Context, ref *provider.Reference) error { +func (fs *owncloudsqlfs) Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { return errtypes.NotSupported("unimplemented") } diff --git a/pkg/storage/fs/s3/s3.go b/pkg/storage/fs/s3/s3.go index 07ea589267b..e624e285618 100644 --- a/pkg/storage/fs/s3/s3.go +++ b/pkg/storage/fs/s3/s3.go @@ -296,7 +296,7 @@ func (fs *s3FS) RefreshLock(ctx context.Context, ref *provider.Reference, lock * } // Unlock removes an existing lock from the given reference -func (fs *s3FS) Unlock(ctx context.Context, ref *provider.Reference) error { +func (fs *s3FS) Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { return errtypes.NotSupported("unimplemented") } diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 5c61f53234f..732d88535aa 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -61,7 +61,7 @@ type FS interface { SetLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error GetLock(ctx context.Context, ref *provider.Reference) (*provider.Lock, error) RefreshLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error - Unlock(ctx context.Context, ref *provider.Reference) error + Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) diff --git a/pkg/storage/utils/decomposedfs/decomposedfs.go b/pkg/storage/utils/decomposedfs/decomposedfs.go index 686fd8977d5..501d3f77fde 100644 --- a/pkg/storage/utils/decomposedfs/decomposedfs.go +++ b/pkg/storage/utils/decomposedfs/decomposedfs.go @@ -311,6 +311,11 @@ func (fs *Decomposedfs) CreateDir(ctx context.Context, ref *provider.Reference) return errtypes.PermissionDenied(filepath.Join(n.ParentID, n.Name)) } + // check lock + if err := n.CheckLock(ctx); err != nil { + return err + } + // verify child does not exist, yet if n, err = n.Child(ctx, name); err != nil { return @@ -433,6 +438,11 @@ func (fs *Decomposedfs) Move(ctx context.Context, oldRef, newRef *provider.Refer return } + // check lock on target + if err := newNode.CheckLock(ctx); err != nil { + return err + } + return fs.tp.Move(ctx, oldNode, newNode) } @@ -521,6 +531,10 @@ func (fs *Decomposedfs) Delete(ctx context.Context, ref *provider.Reference) (er return errtypes.PermissionDenied(filepath.Join(node.ParentID, node.Name)) } + if err := node.CheckLock(ctx); err != nil { + return err + } + return fs.tp.Delete(ctx, node) } @@ -555,20 +569,104 @@ func (fs *Decomposedfs) Download(ctx context.Context, ref *provider.Reference) ( // GetLock returns an existing lock on the given reference func (fs *Decomposedfs) GetLock(ctx context.Context, ref *provider.Reference) (*provider.Lock, error) { - return nil, errtypes.NotSupported("unimplemented") + node, err := fs.lu.NodeFromResource(ctx, ref) + if err != nil { + return nil, errors.Wrap(err, "Decomposedfs: error resolving ref") + } + + if !node.Exists { + err = errtypes.NotFound(filepath.Join(node.ParentID, node.Name)) + return nil, err + } + + ok, err := fs.p.HasPermission(ctx, node, func(rp *provider.ResourcePermissions) bool { + return rp.InitiateFileDownload + }) + switch { + case err != nil: + return nil, errtypes.InternalError(err.Error()) + case !ok: + return nil, errtypes.PermissionDenied(filepath.Join(node.ParentID, node.Name)) + } + return node.ReadLock(ctx) } // SetLock puts a lock on the given reference func (fs *Decomposedfs) SetLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { - return errtypes.NotSupported("unimplemented") + node, err := fs.lu.NodeFromResource(ctx, ref) + if err != nil { + return errors.Wrap(err, "Decomposedfs: error resolving ref") + } + + if !node.Exists { + return errtypes.NotFound(filepath.Join(node.ParentID, node.Name)) + } + + ok, err := fs.p.HasPermission(ctx, node, func(rp *provider.ResourcePermissions) bool { + return rp.InitiateFileUpload + }) + switch { + case err != nil: + return errtypes.InternalError(err.Error()) + case !ok: + return errtypes.PermissionDenied(filepath.Join(node.ParentID, node.Name)) + } + + return node.SetLock(ctx, lock) } // RefreshLock refreshes an existing lock on the given reference func (fs *Decomposedfs) RefreshLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { - return errtypes.NotSupported("unimplemented") + if lock.LockId == "" { + return errtypes.BadRequest("missing lockid") + } + + node, err := fs.lu.NodeFromResource(ctx, ref) + if err != nil { + return errors.Wrap(err, "Decomposedfs: error resolving ref") + } + + if !node.Exists { + return errtypes.NotFound(filepath.Join(node.ParentID, node.Name)) + } + + ok, err := fs.p.HasPermission(ctx, node, func(rp *provider.ResourcePermissions) bool { + return rp.InitiateFileUpload + }) + switch { + case err != nil: + return errtypes.InternalError(err.Error()) + case !ok: + return errtypes.PermissionDenied(filepath.Join(node.ParentID, node.Name)) + } + + return node.RefreshLock(ctx, lock) } // Unlock removes an existing lock from the given reference -func (fs *Decomposedfs) Unlock(ctx context.Context, ref *provider.Reference) error { - return errtypes.NotSupported("unimplemented") +func (fs *Decomposedfs) Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { + if lock.LockId == "" { + return errtypes.BadRequest("missing lockid") + } + + node, err := fs.lu.NodeFromResource(ctx, ref) + if err != nil { + return errors.Wrap(err, "Decomposedfs: error resolving ref") + } + + if !node.Exists { + return errtypes.NotFound(filepath.Join(node.ParentID, node.Name)) + } + + ok, err := fs.p.HasPermission(ctx, node, func(rp *provider.ResourcePermissions) bool { + return rp.InitiateFileUpload // TODO do we need a dedicated permission? + }) + switch { + case err != nil: + return errtypes.InternalError(err.Error()) + case !ok: + return errtypes.PermissionDenied(filepath.Join(node.ParentID, node.Name)) + } + + return node.Unlock(ctx, lock) } diff --git a/pkg/storage/utils/decomposedfs/grants.go b/pkg/storage/utils/decomposedfs/grants.go index 8d0791c3d2c..2920ddd1753 100644 --- a/pkg/storage/utils/decomposedfs/grants.go +++ b/pkg/storage/utils/decomposedfs/grants.go @@ -62,6 +62,11 @@ func (fs *Decomposedfs) AddGrant(ctx context.Context, ref *provider.Reference, g return errtypes.PermissionDenied(filepath.Join(node.ParentID, node.Name)) } + // check lock + if err := node.CheckLock(ctx); err != nil { + return err + } + np := fs.lu.InternalPath(node.ID) e := ace.FromGrant(g) principal, value := e.Marshal() @@ -142,6 +147,11 @@ func (fs *Decomposedfs) RemoveGrant(ctx context.Context, ref *provider.Reference return errtypes.PermissionDenied(filepath.Join(node.ParentID, node.Name)) } + // check lock + if err := node.CheckLock(ctx); err != nil { + return err + } + var attr string if g.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP { attr = xattrs.GrantGroupAcePrefix + g.Grantee.GetGroupId().OpaqueId diff --git a/pkg/storage/utils/decomposedfs/metadata.go b/pkg/storage/utils/decomposedfs/metadata.go index 658feb56047..f45c9ea216d 100644 --- a/pkg/storage/utils/decomposedfs/metadata.go +++ b/pkg/storage/utils/decomposedfs/metadata.go @@ -60,6 +60,11 @@ func (fs *Decomposedfs) SetArbitraryMetadata(ctx context.Context, ref *provider. return errtypes.PermissionDenied(filepath.Join(n.ParentID, n.Name)) } + // check lock + if err := n.CheckLock(ctx); err != nil { + return err + } + nodePath := n.InternalPath() errs := []error{} @@ -148,6 +153,11 @@ func (fs *Decomposedfs) UnsetArbitraryMetadata(ctx context.Context, ref *provide return errtypes.PermissionDenied(filepath.Join(n.ParentID, n.Name)) } + // check lock + if err := n.CheckLock(ctx); err != nil { + return err + } + nodePath := n.InternalPath() errs := []error{} for _, k := range keys { diff --git a/pkg/storage/utils/decomposedfs/node/locks.go b/pkg/storage/utils/decomposedfs/node/locks.go new file mode 100644 index 00000000000..eee9e67a2da --- /dev/null +++ b/pkg/storage/utils/decomposedfs/node/locks.go @@ -0,0 +1,202 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package node + +import ( + "context" + "encoding/json" + "os" + + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/pkg/appctx" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/utils" + "github.com/pkg/errors" +) + +// SetLock sets a lock on the node +func (n *Node) SetLock(ctx context.Context, lock *provider.Lock) error { + // check existing lock + if l, _ := n.ReadLock(ctx); l != nil { + lockID, _ := ctxpkg.ContextGetLockID(ctx) + if l.LockId != lockID { + return errtypes.Locked(l.LockId) + } + err := os.Remove(n.LockFilePath()) + if err != nil { + return err + } + } + + // O_EXCL to make open fail when the file already exists + f, err := os.OpenFile(n.LockFilePath(), os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0600) + if err != nil { + return errors.Wrap(err, "Decomposedfs: could not create lock file") + } + defer f.Close() + + if err := json.NewEncoder(f).Encode(lock); err != nil { + return errors.Wrap(err, "Decomposedfs: could not write lock file") + } + + return nil +} + +// ReadLock reads the lock id for a node +func (n Node) ReadLock(ctx context.Context) (*provider.Lock, error) { + f, err := os.Open(n.LockFilePath()) + if err != nil { + if os.IsNotExist(err) { + return nil, errtypes.NotFound("no lock found") + } + return nil, errors.Wrap(err, "Decomposedfs: could not open lock file") + } + defer f.Close() + + lock := &provider.Lock{} + if err := json.NewDecoder(f).Decode(lock); err != nil { + appctx.GetLogger(ctx).Error().Err(err).Msg("Decomposedfs: could not decode lock file, ignoring") + return nil, errors.Wrap(err, "Decomposedfs: could not read lock file") + } + return lock, nil +} + +// RefreshLock refreshes the node's lock +func (n *Node) RefreshLock(ctx context.Context, lock *provider.Lock) error { + f, err := os.OpenFile(n.LockFilePath(), os.O_RDWR, os.ModeExclusive) + switch { + case os.IsNotExist(err): + return errtypes.PreconditionFailed("lock does not exist") + case err != nil: + return errors.Wrap(err, "Decomposedfs: could not open lock file") + } + defer f.Close() + + oldLock := &provider.Lock{} + if err := json.NewDecoder(f).Decode(oldLock); err != nil { + return errors.Wrap(err, "Decomposedfs: could not read lock") + } + + // check lock + if oldLock.LockId != lock.LockId { + return errtypes.PreconditionFailed("mismatching lock") + } + + u := ctxpkg.ContextMustGetUser(ctx) + if !utils.UserEqual(oldLock.User, u.Id) { + return errtypes.PermissionDenied("cannot refresh lock of another holder") + } + + if !utils.UserEqual(oldLock.User, lock.GetUser()) { + return errtypes.PermissionDenied("cannot change holder when refreshing a lock") + } + + if err := json.NewEncoder(f).Encode(lock); err != nil { + return errors.Wrap(err, "Decomposedfs: could not write lock file") + } + + return nil +} + +// Unlock unlocks the node +func (n *Node) Unlock(ctx context.Context, lock *provider.Lock) error { + f, err := os.OpenFile(n.LockFilePath(), os.O_RDONLY, os.ModeExclusive) + switch { + case os.IsNotExist(err): + return errtypes.PreconditionFailed("lock does not exist") + case err != nil: + return errors.Wrap(err, "Decomposedfs: could not open lock file") + } + defer f.Close() + + oldLock := &provider.Lock{} + if err := json.NewDecoder(f).Decode(oldLock); err != nil { + return errors.Wrap(err, "Decomposedfs: could not read lock") + } + + // check lock + if lock == nil || (oldLock.LockId != lock.LockId) { + return errtypes.Locked(oldLock.LockId) + } + + u := ctxpkg.ContextMustGetUser(ctx) + if !utils.UserEqual(oldLock.User, u.Id) { + return errtypes.PermissionDenied("mismatching holder") + } + + return os.Remove(f.Name()) +} + +// CheckLock compares the context lock with the node lock +func (n *Node) CheckLock(ctx context.Context) error { + lockID, _ := ctxpkg.ContextGetLockID(ctx) + lock, _ := n.ReadLock(ctx) + if lock != nil { + switch lockID { + case "": + return errtypes.Locked(lock.LockId) // no lockid in request + case lock.LockId: + return nil // ok + default: + return errtypes.PreconditionFailed("mismatching lock") + } + } + if lockID != "" { + return errtypes.PreconditionFailed("not locked") + } + return nil // ok +} + +func readLocksIntoOpaque(ctx context.Context, lockPath string, ri *provider.ResourceInfo) { + f, err := os.Open(lockPath) + if err != nil { + appctx.GetLogger(ctx).Error().Err(err).Msg("Decomposedfs: could not open lock file") + return + } + defer f.Close() + + lock := &provider.Lock{} + if err := json.NewDecoder(f).Decode(lock); err != nil { + appctx.GetLogger(ctx).Error().Err(err).Msg("Decomposedfs: could not read lock file") + } + + // reencode to ensure valid json + var b []byte + if b, err = json.Marshal(lock); err != nil { + appctx.GetLogger(ctx).Error().Err(err).Msg("Decomposedfs: could not marshal locks") + } + if ri.Opaque == nil { + ri.Opaque = &types.Opaque{ + Map: map[string]*types.OpaqueEntry{}, + } + } + ri.Opaque.Map["lock"] = &types.OpaqueEntry{ + Decoder: "json", + Value: b, + } + // TODO support advisory locks? +} + +// TODO only exclusive locks for WOPI? or advisory locks? +func (n *Node) hasLocks(ctx context.Context) bool { + _, err := os.Stat(n.LockFilePath()) // FIXME better error checking + return err == nil +} diff --git a/pkg/storage/utils/decomposedfs/node/locks_test.go b/pkg/storage/utils/decomposedfs/node/locks_test.go new file mode 100644 index 00000000000..4512964113c --- /dev/null +++ b/pkg/storage/utils/decomposedfs/node/locks_test.go @@ -0,0 +1,218 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package node_test + +import ( + "context" + "os" + + "github.com/google/uuid" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/node" + helpers "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/testhelpers" +) + +var _ = Describe("Node locks", func() { + var ( + env *helpers.TestEnv + + lock *provider.Lock + wrongLock *provider.Lock + n *node.Node + n2 *node.Node + + otherUser = &userpb.User{ + Id: &userpb.UserId{ + Idp: "idp", + OpaqueId: "foo", + Type: userpb.UserType_USER_TYPE_PRIMARY, + }, + Username: "foo", + } + otherCtx = ctxpkg.ContextSetUser(context.Background(), otherUser) + ) + + BeforeEach(func() { + var err error + env, err = helpers.NewTestEnv() + Expect(err).ToNot(HaveOccurred()) + + lock = &provider.Lock{ + Type: provider.LockType_LOCK_TYPE_EXCL, + User: env.Owner.Id, + LockId: uuid.New().String(), + } + wrongLock = &provider.Lock{ + Type: provider.LockType_LOCK_TYPE_EXCL, + User: env.Owner.Id, + LockId: uuid.New().String(), + } + n = node.New("tobelockedid", "", "tobelocked", 10, "", env.Owner.Id, env.Lookup) + n2 = node.New("neverlockedid", "", "neverlocked", 10, "", env.Owner.Id, env.Lookup) + }) + + AfterEach(func() { + if env != nil { + env.Cleanup() + } + }) + + Describe("SetLock", func() { + It("sets the lock", func() { + _, err := os.Stat(n.LockFilePath()) + Expect(err).To(HaveOccurred()) + + err = n.SetLock(env.Ctx, lock) + Expect(err).ToNot(HaveOccurred()) + + _, err = os.Stat(n.LockFilePath()) + Expect(err).ToNot(HaveOccurred()) + }) + + It("refuses to lock if already locked an existing lock was not provided", func() { + err := n.SetLock(env.Ctx, lock) + Expect(err).ToNot(HaveOccurred()) + + err = n.SetLock(env.Ctx, lock) + Expect(err).To(HaveOccurred()) + + env.Ctx = ctxpkg.ContextSetLockID(env.Ctx, wrongLock.LockId) + err = n.SetLock(env.Ctx, lock) + Expect(err).To(HaveOccurred()) + }) + + It("relocks if the existing lock was provided", func() { + err := n.SetLock(env.Ctx, lock) + Expect(err).ToNot(HaveOccurred()) + + env.Ctx = ctxpkg.ContextSetLockID(env.Ctx, lock.LockId) + err = n.SetLock(env.Ctx, lock) + Expect(err).ToNot(HaveOccurred()) + }) + }) + + Context("with an existing lock", func() { + BeforeEach(func() { + err := n.SetLock(env.Ctx, lock) + Expect(err).ToNot(HaveOccurred()) + }) + + Describe("ReadLock", func() { + It("returns the lock", func() { + l, err := n.ReadLock(env.Ctx) + Expect(err).ToNot(HaveOccurred()) + Expect(l).To(Equal(lock)) + }) + + It("reporst an error when the node wasn't locked", func() { + _, err := n2.ReadLock(env.Ctx) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("no lock found")) + }) + }) + + Describe("RefreshLock", func() { + var ( + newLock *provider.Lock + ) + + JustBeforeEach(func() { + newLock = &provider.Lock{ + Type: provider.LockType_LOCK_TYPE_EXCL, + User: env.Owner.Id, + LockId: lock.LockId, + } + }) + + It("fails when the node is unlocked", func() { + err := n2.RefreshLock(env.Ctx, lock) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("precondition failed")) + }) + + It("refuses to refresh the lock without holding the lock", func() { + newLock.LockId = "somethingsomething" + err := n.RefreshLock(env.Ctx, newLock) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("mismatching")) + }) + + It("refuses to refresh the lock for other users than the lock holder", func() { + err := n.RefreshLock(otherCtx, newLock) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("permission denied")) + }) + + It("refuses to change the lock holder", func() { + newLock.User = otherUser.Id + err := n.RefreshLock(env.Ctx, newLock) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("permission denied")) + }) + + It("refreshes the lock", func() { + err := n.RefreshLock(env.Ctx, newLock) + Expect(err).ToNot(HaveOccurred()) + }) + }) + + Describe("Unlock", func() { + It("refuses to unlock without having a lock", func() { + err := n.Unlock(env.Ctx, nil) + Expect(err.Error()).To(ContainSubstring(lock.LockId)) + }) + + It("refuses to unlock without having the proper lock", func() { + err := n.Unlock(env.Ctx, nil) + Expect(err.Error()).To(ContainSubstring(lock.LockId)) + + err = n.Unlock(env.Ctx, wrongLock) + Expect(err.Error()).To(ContainSubstring(lock.LockId)) + }) + + It("refuses to unlock for others even if they have the lock", func() { + err := n.Unlock(otherCtx, lock) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("mismatching")) + }) + + It("unlocks when the owner uses the lock", func() { + err := n.Unlock(env.Ctx, lock) + Expect(err).ToNot(HaveOccurred()) + + _, err = os.Stat(n.LockFilePath()) + Expect(err).To(HaveOccurred()) + }) + + It("fails to unlock an unlocked node", func() { + err := n.Unlock(env.Ctx, lock) + Expect(err).ToNot(HaveOccurred()) + + err = n.Unlock(env.Ctx, lock) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("lock does not exist")) + }) + }) + }) +}) diff --git a/pkg/storage/utils/decomposedfs/node/node.go b/pkg/storage/utils/decomposedfs/node/node.go index 9be1a2d09f1..f165daaf9cd 100644 --- a/pkg/storage/utils/decomposedfs/node/node.go +++ b/pkg/storage/utils/decomposedfs/node/node.go @@ -33,10 +33,6 @@ import ( "syscall" "time" - "github.com/google/uuid" - "github.com/pkg/errors" - "github.com/pkg/xattr" - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" @@ -48,15 +44,19 @@ import ( "github.com/cs3org/reva/pkg/storage/utils/ace" "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/xattrs" "github.com/cs3org/reva/pkg/utils" + "github.com/google/uuid" + "github.com/pkg/errors" + "github.com/pkg/xattr" ) // Define keys and values used in the node metadata const ( - FavoriteKey = "http://owncloud.org/ns/favorite" - ShareTypesKey = "http://owncloud.org/ns/share-types" - ChecksumsKey = "http://owncloud.org/ns/checksums" - UserShareType = "0" - QuotaKey = "quota" + LockdiscoveryKey = "DAV:lockdiscovery" + FavoriteKey = "http://owncloud.org/ns/favorite" + ShareTypesKey = "http://owncloud.org/ns/share-types" + ChecksumsKey = "http://owncloud.org/ns/checksums" + UserShareType = "0" + QuotaKey = "quota" QuotaUncalculated = "-1" QuotaUnknown = "-2" @@ -255,7 +255,7 @@ func (n *Node) Child(ctx context.Context, name string) (*Node, error) { return c, nil // if the file does not exist we return a node that has Exists = false } - return nil, errors.Wrap(err, "Decomposedfs: Wrap: readlink error") + return nil, errors.Wrap(err, "decomposedfs: Wrap: readlink error") } var c *Node @@ -266,7 +266,7 @@ func (n *Node) Child(ctx context.Context, name string) (*Node, error) { } c.SpaceRoot = n.SpaceRoot } else { - return nil, fmt.Errorf("Decomposedfs: expected '../ prefix, got' %+v", link) + return nil, fmt.Errorf("decomposedfs: expected '../ prefix, got' %+v", link) } return c, nil @@ -275,7 +275,7 @@ func (n *Node) Child(ctx context.Context, name string) (*Node, error) { // Parent returns the parent node func (n *Node) Parent() (p *Node, err error) { if n.ParentID == "" { - return nil, fmt.Errorf("Decomposedfs: root has no parent") + return nil, fmt.Errorf("decomposedfs: root has no parent") } p = &Node{ lu: n.lu, @@ -384,6 +384,11 @@ func (n *Node) InternalPath() string { return n.lu.InternalPath(n.ID) } +// LockFilePath returns the internal path of the lock file of the node +func (n *Node) LockFilePath() string { + return n.lu.InternalPath(n.ID) + ".lock" +} + // CalculateEtag returns a hash of fileid + tmtime (or mtime) func CalculateEtag(nodeID string, tmTime time.Time) (string, error) { return calculateEtag(nodeID, tmTime) @@ -602,6 +607,12 @@ func (n *Node) AsResourceInfo(ctx context.Context, rp *provider.ResourcePermissi } metadata[FavoriteKey] = favorite } + // read locks + if _, ok := mdKeysMap[LockdiscoveryKey]; returnAllKeys || ok { + if n.hasLocks(ctx) { + readLocksIntoOpaque(ctx, n.LockFilePath(), ri) + } + } // share indicator if _, ok := mdKeysMap[ShareTypesKey]; returnAllKeys || ok { @@ -611,7 +622,7 @@ func (n *Node) AsResourceInfo(ctx context.Context, rp *provider.ResourcePermissi } // checksums - if _, ok := mdKeysMap[ChecksumsKey]; (nodeType == provider.ResourceType_RESOURCE_TYPE_FILE) && returnAllKeys || ok { + if _, ok := mdKeysMap[ChecksumsKey]; (nodeType == provider.ResourceType_RESOURCE_TYPE_FILE) && (returnAllKeys || ok) { // TODO which checksum was requested? sha1 adler32 or md5? for now hardcode sha1? readChecksumIntoResourceChecksum(ctx, nodePath, storageprovider.XSSHA1, ri) readChecksumIntoOpaque(ctx, nodePath, storageprovider.XSMD5, ri) diff --git a/pkg/storage/utils/decomposedfs/node/node_test.go b/pkg/storage/utils/decomposedfs/node/node_test.go index 210269cf287..7c59af7fc13 100644 --- a/pkg/storage/utils/decomposedfs/node/node_test.go +++ b/pkg/storage/utils/decomposedfs/node/node_test.go @@ -19,6 +19,7 @@ package node_test import ( + "encoding/json" "time" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" @@ -208,6 +209,27 @@ var _ = Describe("Node", func() { Expect(len(ri.Etag)).To(Equal(34)) Expect(ri.Etag).ToNot(Equal(before)) }) + + It("includes the lock in the Opaque", func() { + lock := &provider.Lock{ + Type: provider.LockType_LOCK_TYPE_EXCL, + User: env.Owner.Id, + LockId: "foo", + } + err := n.SetLock(env.Ctx, lock) + Expect(err).ToNot(HaveOccurred()) + + perms := node.OwnerPermissions() + ri, err := n.AsResourceInfo(env.Ctx, &perms, []string{}, false) + Expect(err).ToNot(HaveOccurred()) + Expect(ri.Opaque).ToNot(BeNil()) + Expect(ri.Opaque.Map["lock"]).ToNot(BeNil()) + + storedLock := &provider.Lock{} + err = json.Unmarshal(ri.Opaque.Map["lock"].Value, storedLock) + Expect(err).ToNot(HaveOccurred()) + Expect(storedLock).To(Equal(lock)) + }) }) }) }) diff --git a/pkg/storage/utils/decomposedfs/node/permissions.go b/pkg/storage/utils/decomposedfs/node/permissions.go index 7694c4ec071..4622045278c 100644 --- a/pkg/storage/utils/decomposedfs/node/permissions.go +++ b/pkg/storage/utils/decomposedfs/node/permissions.go @@ -99,14 +99,14 @@ func NewPermissions(lu PathLookup) *Permissions { func (p *Permissions) AssemblePermissions(ctx context.Context, n *Node) (ap provider.ResourcePermissions, err error) { u, ok := ctxpkg.ContextGetUser(ctx) if !ok { - appctx.GetLogger(ctx).Debug().Interface("node", n).Msg("no user in context, returning default permissions") + appctx.GetLogger(ctx).Debug().Interface("node", n.ID).Msg("no user in context, returning default permissions") return NoPermissions(), nil } // check if the current user is the owner o, err := n.Owner() if err != nil { // TODO check if a parent folder has the owner set? - appctx.GetLogger(ctx).Error().Err(err).Interface("node", n).Msg("could not determine owner, returning default permissions") + appctx.GetLogger(ctx).Error().Err(err).Interface("node", n.ID).Msg("could not determine owner, returning default permissions") return NoPermissions(), err } if o.OpaqueId == "" { @@ -119,7 +119,7 @@ func (p *Permissions) AssemblePermissions(ctx context.Context, n *Node) (ap prov if err == nil && lp == n.lu.ShareFolder() { return ShareFolderPermissions(), nil } - appctx.GetLogger(ctx).Debug().Interface("node", n).Msg("user is owner, returning owner permissions") + appctx.GetLogger(ctx).Debug().Interface("node", n.ID).Msg("user is owner, returning owner permissions") return OwnerPermissions(), nil } // determine root @@ -144,7 +144,7 @@ func (p *Permissions) AssemblePermissions(ctx context.Context, n *Node) (ap prov if np, err := cn.ReadUserPermissions(ctx, u); err == nil { AddPermissions(&ap, &np) } else { - appctx.GetLogger(ctx).Error().Err(err).Interface("node", cn).Msg("error reading permissions") + appctx.GetLogger(ctx).Error().Err(err).Interface("node", cn.ID).Msg("error reading permissions") // continue with next segment } if cn, err = cn.Parent(); err != nil { @@ -152,7 +152,7 @@ func (p *Permissions) AssemblePermissions(ctx context.Context, n *Node) (ap prov } } - appctx.GetLogger(ctx).Debug().Interface("permissions", ap).Interface("node", n).Interface("user", u).Msg("returning agregated permissions") + appctx.GetLogger(ctx).Debug().Interface("permissions", ap).Interface("node", n.ID).Interface("user", u).Msg("returning agregated permissions") return ap, nil } @@ -221,7 +221,7 @@ func nodeHasPermission(ctx context.Context, cn *Node, groupsMap map[string]bool, var grantees []string var err error if grantees, err = cn.ListGrantees(ctx); err != nil { - appctx.GetLogger(ctx).Error().Err(err).Interface("node", cn).Msg("error listing grantees") + appctx.GetLogger(ctx).Error().Err(err).Interface("node", cn.ID).Msg("error listing grantees") return false } @@ -248,14 +248,14 @@ func nodeHasPermission(ctx context.Context, cn *Node, groupsMap map[string]bool, switch { case err == nil: - appctx.GetLogger(ctx).Debug().Interface("node", cn).Str("grant", grantees[i]).Interface("permissions", g.GetPermissions()).Msg("checking permissions") + appctx.GetLogger(ctx).Debug().Interface("node", cn.ID).Str("grant", grantees[i]).Interface("permissions", g.GetPermissions()).Msg("checking permissions") if check(g.GetPermissions()) { return true } case isAttrUnset(err): - appctx.GetLogger(ctx).Error().Interface("node", cn).Str("grant", grantees[i]).Interface("grantees", grantees).Msg("grant vanished from node after listing") + appctx.GetLogger(ctx).Error().Interface("node", cn.ID).Str("grant", grantees[i]).Interface("grantees", grantees).Msg("grant vanished from node after listing") default: - appctx.GetLogger(ctx).Error().Err(err).Interface("node", cn).Str("grant", grantees[i]).Msg("error reading permissions") + appctx.GetLogger(ctx).Error().Err(err).Interface("node", cn.ID).Str("grant", grantees[i]).Msg("error reading permissions") return false } } @@ -266,14 +266,14 @@ func nodeHasPermission(ctx context.Context, cn *Node, groupsMap map[string]bool, func (p *Permissions) getUserAndPermissions(ctx context.Context, n *Node) (*userv1beta1.User, *provider.ResourcePermissions) { u, ok := ctxpkg.ContextGetUser(ctx) if !ok { - appctx.GetLogger(ctx).Debug().Interface("node", n).Msg("no user in context, returning default permissions") + appctx.GetLogger(ctx).Debug().Interface("node", n.ID).Msg("no user in context, returning default permissions") perms := NoPermissions() return nil, &perms } // check if the current user is the owner o, err := n.Owner() if err != nil { - appctx.GetLogger(ctx).Error().Err(err).Interface("node", n).Msg("could not determine owner, returning default permissions") + appctx.GetLogger(ctx).Error().Err(err).Interface("node", n.ID).Msg("could not determine owner, returning default permissions") perms := NoPermissions() return nil, &perms } @@ -284,7 +284,7 @@ func (p *Permissions) getUserAndPermissions(ctx context.Context, n *Node) (*user return nil, &perms } if utils.UserEqual(u.Id, o) { - appctx.GetLogger(ctx).Debug().Interface("node", n).Msg("user is owner, returning owner permissions") + appctx.GetLogger(ctx).Debug().Interface("node", n.ID).Msg("user is owner, returning owner permissions") perms := OwnerPermissions() return u, &perms } diff --git a/pkg/storage/utils/decomposedfs/revisions.go b/pkg/storage/utils/decomposedfs/revisions.go index 09a0d59c5f2..8e50b8fcc23 100644 --- a/pkg/storage/utils/decomposedfs/revisions.go +++ b/pkg/storage/utils/decomposedfs/revisions.go @@ -165,6 +165,11 @@ func (fs *Decomposedfs) RestoreRevision(ctx context.Context, ref *provider.Refer return errtypes.PermissionDenied(filepath.Join(n.ParentID, n.Name)) } + // check lock + if err := n.CheckLock(ctx); err != nil { + return err + } + // move current version to new revision nodePath := fs.lu.InternalPath(kp[0]) var fi os.FileInfo diff --git a/pkg/storage/utils/decomposedfs/tree/tree.go b/pkg/storage/utils/decomposedfs/tree/tree.go index 5bb0f8f0091..1fb125ea2b3 100644 --- a/pkg/storage/utils/decomposedfs/tree/tree.go +++ b/pkg/storage/utils/decomposedfs/tree/tree.go @@ -439,6 +439,9 @@ func (t *Tree) Delete(ctx context.Context, n *node.Node) (err error) { return } + // Remove lock file if it exists + _ = os.Remove(n.LockFilePath()) + // finally remove the entry from the parent dir src := filepath.Join(t.lookup.InternalPath(n.ParentID), n.Name) err = os.Remove(src) @@ -472,6 +475,10 @@ func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, spaceid, key, trashPa } } + if err := targetNode.CheckLock(ctx); err != nil { + return nil, nil, nil, err + } + parent, err := targetNode.Parent() if err != nil { return nil, nil, nil, err diff --git a/pkg/storage/utils/decomposedfs/tree/tree_test.go b/pkg/storage/utils/decomposedfs/tree/tree_test.go index ad191507887..29298a5b661 100644 --- a/pkg/storage/utils/decomposedfs/tree/tree_test.go +++ b/pkg/storage/utils/decomposedfs/tree/tree_test.go @@ -27,6 +27,7 @@ import ( helpers "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/testhelpers" "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/tree" "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/xattrs" + "github.com/google/uuid" "github.com/pkg/xattr" "github.com/stretchr/testify/mock" @@ -70,36 +71,61 @@ var _ = Describe("Tree", func() { }) Describe("Delete", func() { - JustBeforeEach(func() { - _, err := os.Stat(n.InternalPath()) - Expect(err).ToNot(HaveOccurred()) + Context("when the file was locked", func() { + JustBeforeEach(func() { + _, err := os.Stat(n.InternalPath()) + Expect(err).ToNot(HaveOccurred()) - Expect(t.Delete(env.Ctx, n)).To(Succeed()) + lock := &provider.Lock{ + Type: provider.LockType_LOCK_TYPE_EXCL, + User: env.Owner.Id, + LockId: uuid.New().String(), + } + Expect(n.SetLock(env.Ctx, lock)).To(Succeed()) + Expect(t.Delete(env.Ctx, n)).To(Succeed()) - _, err = os.Stat(n.InternalPath()) - Expect(err).To(HaveOccurred()) - }) + _, err = os.Stat(n.InternalPath()) + Expect(err).To(HaveOccurred()) + }) - It("moves the file to the trash", func() { - trashPath := path.Join(env.Root, "trash", n.SpaceRoot.ID, n.ID) - _, err := os.Stat(trashPath) - Expect(err).ToNot(HaveOccurred()) + It("also removes the lock file", func() { + _, err := os.Stat(n.LockFilePath()) + Expect(err).To(HaveOccurred()) + }) }) - It("removes the file from its original location", func() { - _, err := os.Stat(n.InternalPath()) - Expect(err).To(HaveOccurred()) - }) + Context("when the file was not locked", func() { + JustBeforeEach(func() { + _, err := os.Stat(n.InternalPath()) + Expect(err).ToNot(HaveOccurred()) - It("sets the trash origin xattr", func() { - trashPath := path.Join(env.Root, "trash", n.SpaceRoot.ID, n.ID) - attr, err := xattr.Get(trashPath, xattrs.TrashOriginAttr) - Expect(err).ToNot(HaveOccurred()) - Expect(string(attr)).To(Equal("/dir1/file1")) - }) + Expect(t.Delete(env.Ctx, n)).To(Succeed()) + + _, err = os.Stat(n.InternalPath()) + Expect(err).To(HaveOccurred()) + }) + + It("moves the file to the trash", func() { + trashPath := path.Join(env.Root, "trash", n.SpaceRoot.ID, n.ID) + _, err := os.Stat(trashPath) + Expect(err).ToNot(HaveOccurred()) + }) - It("does not delete the blob from the blobstore", func() { - env.Blobstore.AssertNotCalled(GinkgoT(), "Delete", mock.AnythingOfType("string")) + It("removes the file from its original location", func() { + _, err := os.Stat(n.InternalPath()) + Expect(err).To(HaveOccurred()) + }) + + It("sets the trash origin xattr", func() { + trashPath := path.Join(env.Root, "trash", n.SpaceRoot.ID, n.ID) + attr, err := xattr.Get(trashPath, xattrs.TrashOriginAttr) + Expect(err).ToNot(HaveOccurred()) + Expect(string(attr)).To(Equal("/dir1/file1")) + }) + + It("does not delete the blob from the blobstore", func() { + env.Blobstore.AssertNotCalled(GinkgoT(), "Delete", mock.AnythingOfType("string")) + }) }) }) diff --git a/pkg/storage/utils/decomposedfs/upload.go b/pkg/storage/utils/decomposedfs/upload.go index 79f236516f2..3a4a4e17b49 100644 --- a/pkg/storage/utils/decomposedfs/upload.go +++ b/pkg/storage/utils/decomposedfs/upload.go @@ -229,6 +229,11 @@ func (fs *Decomposedfs) NewUpload(ctx context.Context, info tusd.FileInfo) (uplo return nil, errtypes.PermissionDenied(filepath.Join(n.ParentID, n.Name)) } + // check lock + if err := n.CheckLock(ctx); err != nil { + return nil, err + } + info.ID = uuid.New().String() binPath, err := fs.getUploadPath(ctx, info.ID) @@ -462,6 +467,11 @@ func (upload *fileUpload) FinishUpload(ctx context.Context) (err error) { ) n.SpaceRoot = node.New(upload.info.Storage["SpaceRoot"], "", "", 0, "", nil, upload.fs.lu) + // check lock + if err := n.CheckLock(ctx); err != nil { + return err + } + _, err = node.CheckQuota(n.SpaceRoot, uint64(fi.Size())) if err != nil { return err diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index c2310e2a936..9fbb6c9f1b6 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -551,7 +551,7 @@ func (fs *eosfs) RefreshLock(ctx context.Context, ref *provider.Reference, lock } // Unlock removes an existing lock from the given reference -func (fs *eosfs) Unlock(ctx context.Context, ref *provider.Reference) error { +func (fs *eosfs) Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { return errtypes.NotSupported("unimplemented") } diff --git a/pkg/storage/utils/localfs/localfs.go b/pkg/storage/utils/localfs/localfs.go index e6d3a19d44b..6bbb58ac635 100644 --- a/pkg/storage/utils/localfs/localfs.go +++ b/pkg/storage/utils/localfs/localfs.go @@ -731,7 +731,7 @@ func (fs *localfs) RefreshLock(ctx context.Context, ref *provider.Reference, loc } // Unlock removes an existing lock from the given reference -func (fs *localfs) Unlock(ctx context.Context, ref *provider.Reference) error { +func (fs *localfs) Unlock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error { return errtypes.NotSupported("unimplemented") } diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 3a350fa3483..e5370a41b10 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -146,6 +146,15 @@ func LaterTS(t1 *types.Timestamp, t2 *types.Timestamp) *types.Timestamp { return t2 } +// TSNow returns the current UTC timestamp +func TSNow() *types.Timestamp { + t := time.Now().UTC() + return &types.Timestamp{ + Seconds: uint64(t.Unix()), + Nanos: uint32(t.Nanosecond()), + } +} + // ExtractGranteeID returns the ID, user or group, set in the GranteeId object func ExtractGranteeID(grantee *provider.Grantee) (*userpb.UserId, *grouppb.GroupId) { switch t := grantee.Id.(type) { @@ -361,3 +370,29 @@ func GetViewMode(viewMode string) gateway.OpenInAppRequest_ViewMode { return gateway.OpenInAppRequest_VIEW_MODE_INVALID } } + +// AppendPlainToOpaque adds a new key value pair as a plain string on the given opaque and returns it +func AppendPlainToOpaque(o *types.Opaque, key, value string) *types.Opaque { + if o == nil { + o = &types.Opaque{} + } + if o.Map == nil { + o.Map = map[string]*types.OpaqueEntry{} + } + o.Map[key] = &types.OpaqueEntry{ + Decoder: "plain", + Value: []byte(value), + } + return o +} + +// ReadPlainFromOpaque reads a plain string from the given opaque map +func ReadPlainFromOpaque(o *types.Opaque, key string) string { + if o == nil || o.Map == nil { + return "" + } + if e, ok := o.Map[key]; ok && e.Decoder == "plain" { + return string(e.Value) + } + return "" +} diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index 6766ac8f2c4..7d608709a02 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -44,12 +44,6 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiMain/checksums.feature:374](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L374) #### [Webdav LOCK operations](https://github.com/owncloud/ocis/issues/1284) -- [apiWebdavLocks/exclusiveLocks.feature:18](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L18) -- [apiWebdavLocks/exclusiveLocks.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L19) -- [apiWebdavLocks/exclusiveLocks.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L20) -- [apiWebdavLocks/exclusiveLocks.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L21) -- [apiWebdavLocks/exclusiveLocks.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L26) -- [apiWebdavLocks/exclusiveLocks.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L27) - [apiWebdavLocks/exclusiveLocks.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L43) - [apiWebdavLocks/exclusiveLocks.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L44) - [apiWebdavLocks/exclusiveLocks.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L45) @@ -178,6 +172,8 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks/requestsWithToken.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L132) - [apiWebdavLocks/requestsWithToken.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L108) - [apiWebdavLocks/requestsWithToken.feature:109](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L109) +- [apiWebdavLocks/requestsWithToken.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#156) +- [apiWebdavLocks/requestsWithToken.feature:157](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#157) - [apiWebdavLocks/requestsWithToken.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L162) - [apiWebdavLocks2/resharedSharesToShares.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L33) - [apiWebdavLocks2/resharedSharesToShares.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L34) diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index 6457f7e5cbb..6382f216057 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -58,12 +58,6 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiMain/checksums.feature:374](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L374) #### [Webdav LOCK operations](https://github.com/owncloud/ocis/issues/1284) -- [apiWebdavLocks/exclusiveLocks.feature:18](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L18) -- [apiWebdavLocks/exclusiveLocks.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L19) -- [apiWebdavLocks/exclusiveLocks.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L20) -- [apiWebdavLocks/exclusiveLocks.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L21) -- [apiWebdavLocks/exclusiveLocks.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L26) -- [apiWebdavLocks/exclusiveLocks.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L27) - [apiWebdavLocks/exclusiveLocks.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L43) - [apiWebdavLocks/exclusiveLocks.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L44) - [apiWebdavLocks/exclusiveLocks.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/exclusiveLocks.feature#L45) @@ -192,6 +186,8 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks/requestsWithToken.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L132) - [apiWebdavLocks/requestsWithToken.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L108) - [apiWebdavLocks/requestsWithToken.feature:109](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L109) +- [apiWebdavLocks/requestsWithToken.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#156) +- [apiWebdavLocks/requestsWithToken.feature:157](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#157) - [apiWebdavLocks/requestsWithToken.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L162) - [apiWebdavLocks2/resharedSharesToShares.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L33) - [apiWebdavLocks2/resharedSharesToShares.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L34) diff --git a/tests/integration/grpc/storageprovider_test.go b/tests/integration/grpc/storageprovider_test.go index 8f9af283f17..978486676e7 100644 --- a/tests/integration/grpc/storageprovider_test.go +++ b/tests/integration/grpc/storageprovider_test.go @@ -32,7 +32,9 @@ import ( "github.com/cs3org/reva/pkg/storage" "github.com/cs3org/reva/pkg/storage/fs/ocis" jwt "github.com/cs3org/reva/pkg/token/manager/jwt" + "github.com/cs3org/reva/pkg/utils" "github.com/cs3org/reva/tests/helpers" + "github.com/google/uuid" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -542,6 +544,110 @@ var _ = Describe("storage providers", func() { }) } + assertLocking := func(provider string) { + var ( + subdirRef = ref(provider, subdirPath) + lock = &storagep.Lock{ + Type: storagep.LockType_LOCK_TYPE_EXCL, + User: user.Id, + LockId: uuid.New().String(), + } + ) + It("locks, gets, refreshes and unlocks a lock", func() { + lockRes, err := serviceClient.SetLock(ctx, &storagep.SetLockRequest{ + Ref: subdirRef, + Lock: lock, + }) + Expect(err).ToNot(HaveOccurred()) + Expect(lockRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + getRes, err := serviceClient.GetLock(ctx, &storagep.GetLockRequest{ + Ref: subdirRef, + }) + Expect(err).ToNot(HaveOccurred()) + Expect(getRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + Expect(getRes.Lock).To(Equal(lock)) + + refreshRes, err := serviceClient.RefreshLock(ctx, &storagep.RefreshLockRequest{ + Ref: subdirRef, + Lock: lock, + }) + Expect(err).ToNot(HaveOccurred()) + Expect(refreshRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + unlockRes, err := serviceClient.Unlock(ctx, &storagep.UnlockRequest{ + Ref: subdirRef, + Lock: lock, + }) + Expect(err).ToNot(HaveOccurred()) + Expect(unlockRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + }) + + Context("with a locked file", func() { + JustBeforeEach(func() { + lockRes, err := serviceClient.SetLock(ctx, &storagep.SetLockRequest{ + Ref: subdirRef, + Lock: lock, + }) + Expect(err).ToNot(HaveOccurred()) + Expect(lockRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + }) + + It("removes the lock when unlocking", func() { + delRes, err := serviceClient.Delete(ctx, &storagep.DeleteRequest{ + Ref: subdirRef, + }) + Expect(err).ToNot(HaveOccurred()) + Expect(delRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_PERMISSION_DENIED)) + + unlockRes, err := serviceClient.Unlock(ctx, &storagep.UnlockRequest{ + Ref: subdirRef, + Lock: lock, + }) + Expect(err).ToNot(HaveOccurred()) + Expect(unlockRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + delRes, err = serviceClient.Delete(ctx, &storagep.DeleteRequest{ + Ref: subdirRef, + }) + Expect(err).ToNot(HaveOccurred()) + Expect(delRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + }) + + Context("with the owner holding the lock", func() { + It("can initiate an upload", func() { + ulRes, err := serviceClient.InitiateFileUpload(ctx, &storagep.InitiateFileUploadRequest{ + Opaque: utils.AppendPlainToOpaque(nil, "lockid", lock.LockId), + Ref: subdirRef, + }) + Expect(err).ToNot(HaveOccurred()) + Expect(ulRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + }) + + It("can delete the file", func() { + delRes, err := serviceClient.Delete(ctx, &storagep.DeleteRequest{ + Opaque: utils.AppendPlainToOpaque(nil, "lockid", lock.LockId), + Ref: subdirRef, + }) + Expect(err).ToNot(HaveOccurred()) + Expect(delRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_OK)) + + }) + }) + Context("with the owner not holding the lock", func() { + It("can only delete after unlocking the file", func() { + delRes, err := serviceClient.Delete(ctx, &storagep.DeleteRequest{ + Ref: subdirRef, + }) + Expect(err).ToNot(HaveOccurred()) + Expect(delRes.Status.Code).To(Equal(rpcv1beta1.Code_CODE_PERMISSION_DENIED)) + }) + }) + + }) + } + suite := func(provider string, deps map[string]string) { Describe(provider, func() { BeforeEach(func() { @@ -579,6 +685,11 @@ var _ = Describe("storage providers", func() { assertRecycle(provider) assertReferences(provider) assertMetadata(provider) + if provider == "ocis" { + assertLocking(provider) + } else { + PIt("Locking implementation still pending for provider " + provider) + } }) Context("with an existing file /versioned_file", func() { From a703bf1b37556f6e3c599a527fdd075ca77d1d28 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Thu, 10 Feb 2022 13:56:22 +0100 Subject: [PATCH 42/49] Some error cleanup steps in the decomposed FS (#2511) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Some error cleanup steps in the decomposed FS * Hound and changelog added. * Remove punctuation * Make CI happy * Improved error logging. Co-authored-by: David Christofas * Update pkg/storage/utils/decomposedfs/decomposedfs.go Co-authored-by: David Christofas Co-authored-by: David Christofas Co-authored-by: Jörn Friedrich Dreyer --- changelog/unreleased/clean-dfs1.md | 5 ++ .../utils/decomposedfs/decomposedfs.go | 64 +++++++++++++++---- pkg/storage/utils/decomposedfs/spaces.go | 4 +- 3 files changed, 59 insertions(+), 14 deletions(-) create mode 100644 changelog/unreleased/clean-dfs1.md diff --git a/changelog/unreleased/clean-dfs1.md b/changelog/unreleased/clean-dfs1.md new file mode 100644 index 00000000000..436b05f5421 --- /dev/null +++ b/changelog/unreleased/clean-dfs1.md @@ -0,0 +1,5 @@ +Enhancement: Error handling cleanup in decomposed FS + +- Avoid inconsensitencies by cleaning up actions in case of err + +https://github.com/cs3org/reva/pull/2511 diff --git a/pkg/storage/utils/decomposedfs/decomposedfs.go b/pkg/storage/utils/decomposedfs/decomposedfs.go index 501d3f77fde..002baf8ea8a 100644 --- a/pkg/storage/utils/decomposedfs/decomposedfs.go +++ b/pkg/storage/utils/decomposedfs/decomposedfs.go @@ -217,6 +217,17 @@ func (fs *Decomposedfs) CreateHome(ctx context.Context) (err error) { } return nil }) + + // make sure to delete the created directory if things go wrong + defer func() { + if err != nil { + // do not catch the error to not shadow the original error + if tmpErr := fs.tp.Delete(ctx, n); tmpErr != nil { + appctx.GetLogger(ctx).Error().Err(tmpErr).Msg("Can not revert file system change after error") + } + } + }() + if err != nil { return } @@ -333,6 +344,9 @@ func (fs *Decomposedfs) CreateDir(ctx context.Context, ref *provider.Reference) // mark the home node as the end of propagation if err = xattr.Set(nodePath, xattrs.PropagationAttr, []byte("1")); err != nil { appctx.GetLogger(ctx).Error().Err(err).Interface("node", n).Msg("could not mark node to propagate") + + // FIXME: This does not return an error at all, but results in a severe situation that the + // part tree is not marked for propagation return } } @@ -345,9 +359,10 @@ func (fs *Decomposedfs) TouchFile(ctx context.Context, ref *provider.Reference) } // CreateReference creates a reference as a node folder with the target stored in extended attributes -// There is no difference between the /Shares folder and normal nodes because the storage is not supposed to be accessible without the storage provider. -// In effect everything is a shadow namespace. +// There is no difference between the /Shares folder and normal nodes because the storage is not supposed to be accessible +// without the storage provider. In effect everything is a shadow namespace. // To mimic the eos and owncloud driver we only allow references as children of the "/Shares" folder +// FIXME: This comment should explain briefly what a reference is in this context. func (fs *Decomposedfs) CreateReference(ctx context.Context, p string, targetURI *url.URL) (err error) { ctx, span := rtrace.Provider.Tracer("reva").Start(ctx, "CreateReference") defer span.End() @@ -368,39 +383,62 @@ func (fs *Decomposedfs) CreateReference(ctx context.Context, p string, targetURI } // create Shares folder if it does not exist - var n *node.Node - if n, err = fs.lu.NodeFromResource(ctx, &provider.Reference{Path: fs.o.ShareFolder}); err != nil { + var parentNode *node.Node + var parentCreated, childCreated bool // defaults to false + if parentNode, err = fs.lu.NodeFromResource(ctx, &provider.Reference{Path: fs.o.ShareFolder}); err != nil { err := errtypes.InternalError(err.Error()) span.SetStatus(codes.Error, err.Error()) return err - } else if !n.Exists { - if err = fs.tp.CreateDir(ctx, n); err != nil { + } else if !parentNode.Exists { + if err = fs.tp.CreateDir(ctx, parentNode); err != nil { span.SetStatus(codes.Error, err.Error()) return err } - } + parentCreated = true + } + + var childNode *node.Node + // clean up directories created here on error + defer func() { + if err != nil { + // do not catch the error to not shadow the original error + if childCreated && childNode != nil { + if tmpErr := fs.tp.Delete(ctx, childNode); tmpErr != nil { + appctx.GetLogger(ctx).Error().Err(tmpErr).Str("node_id", childNode.ID).Msg("Can not clean up child node after error") + } + } + if parentCreated && parentNode != nil { + if tmpErr := fs.tp.Delete(ctx, parentNode); tmpErr != nil { + appctx.GetLogger(ctx).Error().Err(tmpErr).Str("node_id", parentNode.ID).Msg("Can not clean up parent node after error") + } + + } + } + }() - if n, err = n.Child(ctx, parts[1]); err != nil { + if childNode, err = parentNode.Child(ctx, parts[1]); err != nil { return errtypes.InternalError(err.Error()) } - if n.Exists { + if childNode.Exists { // TODO append increasing number to mountpoint name err := errtypes.AlreadyExists(p) span.SetStatus(codes.Error, err.Error()) return err } - if err := fs.tp.CreateDir(ctx, n); err != nil { + if err := fs.tp.CreateDir(ctx, childNode); err != nil { span.SetStatus(codes.Error, err.Error()) return err } + childCreated = true - internal := n.InternalPath() - if err := xattr.Set(internal, xattrs.ReferenceAttr, []byte(targetURI.String())); err != nil { + internalPath := childNode.InternalPath() + if err := xattr.Set(internalPath, xattrs.ReferenceAttr, []byte(targetURI.String())); err != nil { + // the reference could not be set - that would result in an lost reference? err := errors.Wrapf(err, "Decomposedfs: error setting the target %s on the reference file %s", targetURI.String(), - internal, + internalPath, ) span.SetStatus(codes.Error, err.Error()) return err diff --git a/pkg/storage/utils/decomposedfs/spaces.go b/pkg/storage/utils/decomposedfs/spaces.go index d26a3581073..d3b7c98c6ec 100644 --- a/pkg/storage/utils/decomposedfs/spaces.go +++ b/pkg/storage/utils/decomposedfs/spaces.go @@ -505,13 +505,15 @@ func (fs *Decomposedfs) createStorageSpace(ctx context.Context, spaceType, space if err != nil { if isAlreadyExists(err) { appctx.GetLogger(ctx).Debug().Err(err).Str("space", spaceID).Str("spacetype", spaceType).Msg("symlink already exists") + // FIXME: is it ok to wipe this err if the symlink already exists? + err = nil } else { // TODO how should we handle error cases here? appctx.GetLogger(ctx).Error().Err(err).Str("space", spaceID).Str("spacetype", spaceType).Msg("could not create symlink") } } - return nil + return err } func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, spaceType, nodePath string, canListAllSpaces bool) (*provider.StorageSpace, error) { From 32a098c06f6f9f4525f177eabd0952a55bb567c6 Mon Sep 17 00:00:00 2001 From: Andre Duffeck Date: Thu, 10 Feb 2022 14:48:17 +0100 Subject: [PATCH 43/49] Upgrade to ginkgo v2, increase test coverage (#2526) * Upgrade to ginkgo v2 * Fix root info being counted twice * Do not try to list child non-containers * Improve generating response xml * DRY up code * Increase test coverage, port EncodePath benchmark to ginkgo Also add an assertion that its performanc does not decrease too much. * Add changelog * Fix hound issue * Add missing license header * Fix linter issue --- changelog/unreleased/upgrade-ginkgo.md | 3 + go.mod | 4 +- go.sum | 10 +- .../sharesstorageprovider_suite_test.go | 2 +- .../sharesstorageprovider_test.go | 2 +- .../owncloud/ocdav/net/context_test.go | 59 ++ .../owncloud/ocdav/net/net_suite_test.go | 31 + .../services/owncloud/ocdav/net/net_test.go | 112 ++- .../owncloud/ocdav/ocdav_suite_test.go | 2 +- .../services/owncloud/ocdav/ocdav_test.go | 13 - .../owncloud/ocdav/propfind/propfind.go | 240 ++++--- .../ocdav/propfind/propfind_suite_test.go | 2 +- .../owncloud/ocdav/propfind/propfind_test.go | 674 +++++++++++++++++- .../apps/sharing/shares/pending_test.go | 2 +- .../apps/sharing/shares/shares_suite_test.go | 2 +- .../apps/sharing/shares/shares_test.go | 2 +- .../manager/nextcloud/nextcloud_suite_test.go | 2 +- pkg/auth/manager/nextcloud/nextcloud_test.go | 2 +- .../accounts/accounts_suite_test.go | 2 +- .../owncloudsql/accounts/accounts_test.go | 2 +- .../manager/nextcloud/nextcloud_suite_test.go | 2 +- .../share/manager/nextcloud/nextcloud_test.go | 2 +- pkg/share/manager/sql/sql_suite_test.go | 2 +- pkg/share/manager/sql/sql_test.go | 2 +- .../fs/nextcloud/nextcloud_suite_test.go | 2 +- pkg/storage/fs/nextcloud/nextcloud_test.go | 2 +- .../fs/ocis/blobstore/blobstore_suite_test.go | 2 +- .../fs/ocis/blobstore/blobstore_test.go | 2 +- pkg/storage/fs/ocis/ocis_suite_test.go | 2 +- pkg/storage/fs/ocis/ocis_test.go | 2 +- .../filecache/filecache_suite_test.go | 2 +- .../owncloudsql/filecache/filecache_test.go | 2 +- pkg/storage/fs/s3ng/option_test.go | 2 +- pkg/storage/fs/s3ng/s3ng_suite_test.go | 2 +- pkg/storage/fs/s3ng/s3ng_test.go | 2 +- .../registry/spaces/spaces_suite_test.go | 2 +- pkg/storage/registry/spaces/spaces_test.go | 2 +- .../registry/static/static_suite_test.go | 2 +- pkg/storage/registry/static/static_test.go | 2 +- pkg/storage/utils/ace/ace_suite_test.go | 2 +- pkg/storage/utils/ace/ace_test.go | 2 +- .../decomposedfs_concurrency_test.go | 2 +- .../decomposedfs/decomposedfs_suite_test.go | 2 +- .../utils/decomposedfs/decomposedfs_test.go | 2 +- pkg/storage/utils/decomposedfs/grants_test.go | 2 +- pkg/storage/utils/decomposedfs/lookup_test.go | 2 +- .../utils/decomposedfs/node/locks_test.go | 2 +- .../decomposedfs/node/node_suite_test.go | 2 +- .../utils/decomposedfs/node/node_test.go | 2 +- .../options/options_suite_test.go | 2 +- .../decomposedfs/options/options_test.go | 2 +- .../utils/decomposedfs/recycle_test.go | 2 +- .../decomposedfs/tree/tree_suite_test.go | 2 +- .../utils/decomposedfs/tree/tree_test.go | 2 +- pkg/storage/utils/decomposedfs/upload_test.go | 2 +- .../manager/nextcloud/nextcloud_suite_test.go | 2 +- pkg/user/manager/nextcloud/nextcloud_test.go | 2 +- .../accounts/accounts_suite_test.go | 2 +- .../owncloudsql/accounts/accounts_test.go | 2 +- .../gateway_storageprovider_static_test.go | 2 +- .../grpc/gateway_storageprovider_test.go | 2 +- tests/integration/grpc/grpc_suite_test.go | 2 +- .../integration/grpc/storageprovider_test.go | 2 +- tests/integration/grpc/userprovider_test.go | 2 +- 64 files changed, 1037 insertions(+), 219 deletions(-) create mode 100644 changelog/unreleased/upgrade-ginkgo.md create mode 100644 internal/http/services/owncloud/ocdav/net/context_test.go create mode 100644 internal/http/services/owncloud/ocdav/net/net_suite_test.go diff --git a/changelog/unreleased/upgrade-ginkgo.md b/changelog/unreleased/upgrade-ginkgo.md new file mode 100644 index 00000000000..dca15641d0f --- /dev/null +++ b/changelog/unreleased/upgrade-ginkgo.md @@ -0,0 +1,3 @@ +Enhancement: upgrade ginkgo to v2 + +https://github.com/cs3org/reva/pull/2526 \ No newline at end of file diff --git a/go.mod b/go.mod index 21feb75ecda..682a62c9460 100644 --- a/go.mod +++ b/go.mod @@ -48,8 +48,8 @@ require ( github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/mapstructure v1.4.3 - github.com/onsi/ginkgo v1.16.5 - github.com/onsi/gomega v1.18.0 + github.com/onsi/ginkgo/v2 v2.0.0 + github.com/onsi/gomega v1.18.1 github.com/pkg/errors v0.9.1 github.com/pkg/xattr v0.4.4 github.com/pquerna/cachecontrol v0.1.0 // indirect diff --git a/go.sum b/go.sum index 3c41296610b..a86e76abb8e 100644 --- a/go.sum +++ b/go.sum @@ -158,7 +158,6 @@ github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gdexlab/go-render v1.0.1 h1:rxqB3vo5s4n1kF0ySmoNeSPRYkEsyHgln4jFIQY7v0U= github.com/gdexlab/go-render v1.0.1/go.mod h1:wRi5nW2qfjiGj4mPukH4UV0IknS1cHD4VgFTmJX5JzM= @@ -563,7 +562,6 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= @@ -573,17 +571,16 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.18.0 h1:ngbYoRctxjl8SiF7XgP0NxBFbfHcg3wfHMMaFHWwMTM= -github.com/onsi/gomega v1.18.0/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -1192,7 +1189,6 @@ gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_suite_test.go b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_suite_test.go index fceaad3ed32..36dbf5036ab 100644 --- a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_suite_test.go +++ b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_suite_test.go @@ -21,7 +21,7 @@ package sharesstorageprovider_test import ( "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go index cbd1628ca8b..bdd41097964 100644 --- a/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go +++ b/internal/grpc/services/sharesstorageprovider/sharesstorageprovider_test.go @@ -37,7 +37,7 @@ import ( "github.com/cs3org/reva/pkg/utils" "google.golang.org/grpc" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/stretchr/testify/mock" ) diff --git a/internal/http/services/owncloud/ocdav/net/context_test.go b/internal/http/services/owncloud/ocdav/net/context_test.go new file mode 100644 index 00000000000..af653e74778 --- /dev/null +++ b/internal/http/services/owncloud/ocdav/net/context_test.go @@ -0,0 +1,59 @@ +// Copyright 2018-2022 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package net_test + +import ( + "context" + + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + ctxpkg "github.com/cs3org/reva/pkg/ctx" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Net", func() { + var ( + alice = &userpb.User{ + Id: &userpb.UserId{ + OpaqueId: "alice", + }, + Username: "alice", + } + bob = &userpb.User{ + Id: &userpb.UserId{ + OpaqueId: "bob", + }, + Username: "bob", + } + aliceCtx = ctxpkg.ContextSetUser(context.Background(), alice) + bobCtx = ctxpkg.ContextSetUser(context.Background(), bob) + ) + + Describe("IsCurrentUserOwner", func() { + It("returns true", func() { + Expect(net.IsCurrentUserOwner(aliceCtx, alice.Id)).To(BeTrue()) + }) + + It("returns false", func() { + Expect(net.IsCurrentUserOwner(bobCtx, alice.Id)).To(BeFalse()) + }) + }) +}) diff --git a/internal/http/services/owncloud/ocdav/net/net_suite_test.go b/internal/http/services/owncloud/ocdav/net/net_suite_test.go new file mode 100644 index 00000000000..5760c91b472 --- /dev/null +++ b/internal/http/services/owncloud/ocdav/net/net_suite_test.go @@ -0,0 +1,31 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package net_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestNet(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Net Suite") +} diff --git a/internal/http/services/owncloud/ocdav/net/net_test.go b/internal/http/services/owncloud/ocdav/net/net_test.go index 2b0e2c32aaa..17c8eb27a07 100644 --- a/internal/http/services/owncloud/ocdav/net/net_test.go +++ b/internal/http/services/owncloud/ocdav/net/net_test.go @@ -16,40 +16,78 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -package net - -import "testing" - -func TestParseDepth(t *testing.T) { - tests := map[string]Depth{ - "": DepthOne, - "0": DepthZero, - "1": DepthOne, - "infinity": DepthInfinity, - } - - for input, expected := range tests { - parsed, err := ParseDepth(input) - if err != nil { - t.Errorf("failed to parse depth %s", input) - } - if parsed != expected { - t.Errorf("parseDepth returned %s expected %s", parsed.String(), expected.String()) - } - } - - _, err := ParseDepth("invalid") - if err == nil { - t.Error("parse depth didn't return an error for invalid depth: invalid") - } -} - -var result Depth - -func BenchmarkParseDepth(b *testing.B) { - inputs := []string{"", "0", "1", "infinity", "INFINITY"} - size := len(inputs) - for i := 0; i < b.N; i++ { - result, _ = ParseDepth(inputs[i%size]) - } -} +package net_test + +import ( + "time" + + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gmeasure" +) + +var _ = Describe("Net", func() { + DescribeTable("TestParseDepth", + func(v string, expectSuccess bool, expectedValue net.Depth) { + parsed, err := net.ParseDepth(v) + Expect(err == nil).To(Equal(expectSuccess)) + Expect(parsed).To(Equal(expectedValue)) + }, + Entry("default", "", true, net.DepthOne), + Entry("0", "0", true, net.DepthZero), + Entry("1", "1", true, net.DepthOne), + Entry("infinity", "infinity", true, net.DepthInfinity), + Entry("invalid", "invalid", false, net.Depth(""))) + + Describe("ParseDepth", func() { + It("is reasonably fast", func() { + experiment := NewExperiment("Parsing depth headers") + AddReportEntry(experiment.Name, experiment) + + inputs := []string{"", "0", "1", "infinity", "INFINITY"} + size := len(inputs) + experiment.Sample(func(i int) { + experiment.MeasureDuration("parsing", func() { + _, _ = net.ParseDepth(inputs[i%size]) + }) + }, SamplingConfig{Duration: time.Second}) + + encodingStats := experiment.GetStats("parsing") + medianDuration := encodingStats.DurationFor(StatMedian) + + Expect(medianDuration).To(BeNumerically("<", 3*time.Millisecond)) + }) + }) + + Describe("EncodePath", func() { + It("encodes paths", func() { + Expect(net.EncodePath("foo")).To(Equal("foo")) + Expect(net.EncodePath("/some/path/Folder %^*(#1)")).To(Equal("/some/path/Folder%20%25%5e%2a(%231)")) + }) + + /* + The encodePath method as it is implemented currently is terribly inefficient. + As soon as there are a few special characters which need to be escaped the allocation count rises and the time spent too. + Adding more special characters increases the allocations and the time spent can rise up to a few milliseconds. + Granted this is not a lot on it's own but when a user has tens or hundreds of paths which need to be escaped and contain a few special characters + then this method alone will cost a huge amount of time. + */ + It("is reasonably fast", func() { + experiment := NewExperiment("Encoding paths") + AddReportEntry(experiment.Name, experiment) + + experiment.Sample(func(idx int) { + experiment.MeasureDuration("encoding", func() { + _ = net.EncodePath("/some/path/Folder %^*(#1)") + }) + }, SamplingConfig{Duration: time.Second}) + + encodingStats := experiment.GetStats("encoding") + medianDuration := encodingStats.DurationFor(StatMedian) + + Expect(medianDuration).To(BeNumerically("<", 10*time.Millisecond)) + }) + }) +}) diff --git a/internal/http/services/owncloud/ocdav/ocdav_suite_test.go b/internal/http/services/owncloud/ocdav/ocdav_suite_test.go index c31853b080f..26639ca7811 100644 --- a/internal/http/services/owncloud/ocdav/ocdav_suite_test.go +++ b/internal/http/services/owncloud/ocdav/ocdav_suite_test.go @@ -21,7 +21,7 @@ package ocdav_test import ( "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/internal/http/services/owncloud/ocdav/ocdav_test.go b/internal/http/services/owncloud/ocdav/ocdav_test.go index 98c60afa33c..bcf63f63363 100644 --- a/internal/http/services/owncloud/ocdav/ocdav_test.go +++ b/internal/http/services/owncloud/ocdav/ocdav_test.go @@ -29,19 +29,6 @@ import ( "github.com/cs3org/reva/pkg/utils/resourceid" ) -/* -The encodePath method as it is implemented currently is terribly inefficient. -As soon as there are a few special characters which need to be escaped the allocation count rises and the time spent too. -Adding more special characters increases the allocations and the time spent can rise up to a few milliseconds. -Granted this is not a lot on it's own but when a user has tens or hundreds of paths which need to be escaped and contain a few special characters -then this method alone will cost a huge amount of time. -*/ -func BenchmarkEncodePath(b *testing.B) { - for i := 0; i < b.N; i++ { - _ = net.EncodePath("/some/path/Folder %^*(#1)") - } -} - func TestWrapResourceID(t *testing.T) { expected := "c3RvcmFnZWlkOm9wYXF1ZWlk" wrapped := resourceid.OwnCloudResourceIDWrap(&providerv1beta1.ResourceId{StorageId: "storageid", OpaqueId: "opaqueid"}) diff --git a/internal/http/services/owncloud/ocdav/propfind/propfind.go b/internal/http/services/owncloud/ocdav/propfind/propfind.go index b1aadcf8ee8..59043a39501 100644 --- a/internal/http/services/owncloud/ocdav/propfind/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind/propfind.go @@ -19,7 +19,6 @@ package propfind import ( - "bytes" "context" "encoding/json" "encoding/xml" @@ -57,7 +56,102 @@ import ( //go:generate mockery -name GatewayClient -// GatewayClient is the interface that's being uses to interact with the gateway +type countingReader struct { + n int + r io.Reader +} + +// Props represents properties related to a resource +// http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for propfind) +type Props []xml.Name + +// XML holds the xml representation of a propfind +// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propfind +type XML struct { + XMLName xml.Name `xml:"DAV: propfind"` + Allprop *struct{} `xml:"DAV: allprop"` + Propname *struct{} `xml:"DAV: propname"` + Prop Props `xml:"DAV: prop"` + Include Props `xml:"DAV: include"` +} + +// PropstatXML holds the xml representation of a propfind response +// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat +type PropstatXML struct { + // Prop requires DAV: to be the default namespace in the enclosing + // XML. This is due to the standard encoding/xml package currently + // not honoring namespace declarations inside a xmltag with a + // parent element for anonymous slice elements. + // Use of multistatusWriter takes care of this. + Prop []*props.PropertyXML `xml:"d:prop>_ignored_"` + Status string `xml:"d:status"` + Error *errors.ErrorXML `xml:"d:error"` + ResponseDescription string `xml:"d:responsedescription,omitempty"` +} + +// ResponseXML holds the xml representation of a propfind response +type ResponseXML struct { + XMLName xml.Name `xml:"d:response"` + Href string `xml:"d:href"` + Propstat []PropstatXML `xml:"d:propstat"` + Status string `xml:"d:status,omitempty"` + Error *errors.ErrorXML `xml:"d:error"` + ResponseDescription string `xml:"d:responsedescription,omitempty"` +} + +// MultiStatusResponseXML holds the xml representation of a multistatus propfind response +type MultiStatusResponseXML struct { + XMLName xml.Name `xml:"d:multistatus"` + XmlnsS string `xml:"xmlns:s,attr,omitempty"` + XmlnsD string `xml:"xmlns:d,attr,omitempty"` + XmlnsOC string `xml:"xmlns:oc,attr,omitempty"` + + Responses []*ResponseXML `xml:"d:response"` +} + +// ResponseUnmarshalXML is a workaround for https://github.com/golang/go/issues/13400 +type ResponseUnmarshalXML struct { + XMLName xml.Name `xml:"response"` + Href string `xml:"href"` + Propstat []PropstatUnmarshalXML `xml:"propstat"` + Status string `xml:"status,omitempty"` + Error *errors.ErrorXML `xml:"d:error"` + ResponseDescription string `xml:"responsedescription,omitempty"` +} + +// MultiStatusResponseUnmarshalXML is a workaround for https://github.com/golang/go/issues/13400 +type MultiStatusResponseUnmarshalXML struct { + XMLName xml.Name `xml:"multistatus"` + XmlnsS string `xml:"xmlns:s,attr,omitempty"` + XmlnsD string `xml:"xmlns:d,attr,omitempty"` + XmlnsOC string `xml:"xmlns:oc,attr,omitempty"` + + Responses []*ResponseUnmarshalXML `xml:"response"` +} + +// PropstatUnmarshalXML is a workaround for https://github.com/golang/go/issues/13400 +type PropstatUnmarshalXML struct { + // Prop requires DAV: to be the default namespace in the enclosing + // XML. This is due to the standard encoding/xml package currently + // not honoring namespace declarations inside a xmltag with a + // parent element for anonymous slice elements. + // Use of multistatusWriter takes care of this. + Prop []*props.PropertyXML `xml:"prop"` + Status string `xml:"status"` + Error *errors.ErrorXML `xml:"d:error"` + ResponseDescription string `xml:"responsedescription,omitempty"` +} + +// NewMultiStatusResponseXML returns a preconfigured instance of MultiStatusResponseXML +func NewMultiStatusResponseXML() *MultiStatusResponseXML { + return &MultiStatusResponseXML{ + XmlnsD: "DAV:", + XmlnsS: "http://sabredav.org/ns", + XmlnsOC: "http://owncloud.org/ns", + } +} + +// GatewayClient is the interface that's being used to interact with the gateway type GatewayClient interface { gateway.GatewayAPIClient } @@ -106,7 +200,7 @@ func (p *Handler) HandlePathPropfind(w http.ResponseWriter, r *http.Request, ns return } - spaces, rpcStatus, err := spacelookup.LookUpStorageSpacesForPathWithChildren(ctx, client.(gateway.GatewayAPIClient), fn) + spaces, rpcStatus, err := spacelookup.LookUpStorageSpacesForPathWithChildren(ctx, client, fn) if err != nil { sublog.Error().Err(err).Msg("error sending a grpc request") w.WriteHeader(http.StatusInternalServerError) @@ -147,7 +241,7 @@ func (p *Handler) HandleSpacesPropfind(w http.ResponseWriter, r *http.Request, s } // retrieve a specific storage space - space, rpcStatus, err := spacelookup.LookUpStorageSpaceByID(ctx, client.(gateway.GatewayAPIClient), spaceID) + space, rpcStatus, err := spacelookup.LookUpStorageSpaceByID(ctx, client, spaceID) if err != nil { sublog.Error().Err(err).Msg("error looking up the space by id") w.WriteHeader(http.StatusInternalServerError) @@ -288,7 +382,7 @@ func (p *Handler) getResourceInfos(ctx context.Context, w http.ResponseWriter, r // TODO separate stats to the path or to the children, after statting all children update the mtime/etag // TODO get mtime, and size from space as well, so we no longer have to stat here? spaceRef := spacelookup.MakeRelativeReference(space, requestPath, spacesPropfind) - info, status, err := p.statSpace(ctx, client.(gateway.GatewayAPIClient), space, spaceRef, metadataKeys) + info, status, err := p.statSpace(ctx, client, space, spaceRef, metadataKeys) if err != nil || status.Code != rpc.Code_CODE_OK { continue } @@ -303,13 +397,9 @@ func (p *Handler) getResourceInfos(ctx context.Context, w http.ResponseWriter, r spaceMap[info] = spaceRef spaceInfos = append(spaceInfos, info) - - if rootInfo == nil && requestPath == info.Path || spacesPropfind && requestPath == path.Join("/", info.Path) { + if rootInfo == nil && (requestPath == info.Path || (spacesPropfind && requestPath == path.Join("/", info.Path))) { rootInfo = info - } - - // Check if the space is a child of the requested path - if requestPath != spacePath && strings.HasPrefix(spacePath, requestPath) { + } else if requestPath != spacePath && strings.HasPrefix(spacePath, requestPath) { // Check if the space is a child of the requested path // aggregate child metadata aggregatedChildSize += info.Size if mostRecentChildInfo == nil { @@ -348,36 +438,45 @@ func (p *Handler) getResourceInfos(ctx context.Context, w http.ResponseWriter, r resourceInfos := []*provider.ResourceInfo{ rootInfo, // PROPFIND always includes the root resource } - if rootInfo.Type == provider.ResourceType_RESOURCE_TYPE_FILE { // no need to stat any other spaces, we got our file stat already return resourceInfos, true, true } childInfos := map[string]*provider.ResourceInfo{} + addChild := func(spaceInfo *provider.ResourceInfo) { + if spaceInfo == rootInfo { + return // already accounted for + } + childPath := strings.TrimPrefix(spaceInfo.Path, requestPath) + childName, tail := router.ShiftPath(childPath) + if tail != "/" { + spaceInfo.Type = provider.ResourceType_RESOURCE_TYPE_CONTAINER + spaceInfo.Checksum = nil + // TODO unset opaque checksum + } + spaceInfo.Path = path.Join(requestPath, childName) + if existingChild, ok := childInfos[childName]; ok { + // use most recent child + if existingChild.Mtime == nil || (spaceInfo.Mtime != nil && utils.TSToUnixNano(spaceInfo.Mtime) > utils.TSToUnixNano(existingChild.Mtime)) { + childInfos[childName].Mtime = spaceInfo.Mtime + childInfos[childName].Etag = spaceInfo.Etag + childInfos[childName].Size += spaceInfo.Size + } + // only update fileid if the resource is a direct child + if tail == "/" { + childInfos[childName].Id = spaceInfo.Id + } + } else { + childInfos[childName] = spaceInfo + } + } // then add children for _, spaceInfo := range spaceInfos { switch { case !spacesPropfind && spaceInfo.Type != provider.ResourceType_RESOURCE_TYPE_CONTAINER && depth != net.DepthInfinity: - // The propfind is requested for a file that exists - - childPath := strings.TrimPrefix(spaceInfo.Path, requestPath) - childName, tail := router.ShiftPath(childPath) - if tail != "/" { - spaceInfo.Type = provider.ResourceType_RESOURCE_TYPE_CONTAINER - spaceInfo.Checksum = nil - // TODO unset opaque checksum - } - spaceInfo.Path = path.Join(requestPath, childName) - if existingChild, ok := childInfos[childName]; ok { - // use most recent child - if existingChild.Mtime == nil || (spaceInfo.Mtime != nil && utils.TSToUnixNano(spaceInfo.Mtime) > utils.TSToUnixNano(existingChild.Mtime)) { - childInfos[childName] = spaceInfo - } - } else { - childInfos[childName] = spaceInfo - } + addChild(spaceInfo) case spaceInfo.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER && depth == net.DepthOne: switch { @@ -402,27 +501,7 @@ func (p *Handler) getResourceInfos(ctx context.Context, w http.ResponseWriter, r } resourceInfos = append(resourceInfos, res.Infos...) case strings.HasPrefix(spaceInfo.Path, requestPath): // space is a deep child of the requested path - childPath := strings.TrimPrefix(spaceInfo.Path, requestPath) - childName, tail := router.ShiftPath(childPath) - if tail != "/" { - spaceInfo.Type = provider.ResourceType_RESOURCE_TYPE_CONTAINER - spaceInfo.Checksum = nil - // TODO unset opaque checksum - } - if existingChild, ok := childInfos[childName]; ok { - // use most recent child - if existingChild.Mtime == nil || (spaceInfo.Mtime != nil && utils.TSToUnixNano(spaceInfo.Mtime) > utils.TSToUnixNano(existingChild.Mtime)) { - childInfos[childName].Mtime = spaceInfo.Mtime - childInfos[childName].Etag = spaceInfo.Etag - } - // only update fileid if the resource is a direct child - if tail == "/" { - childInfos[childName].Id = spaceInfo.Id - } - } else { - childInfos[childName] = spaceInfo - } - spaceInfo.Path = path.Join(requestPath, childName) + addChild(spaceInfo) default: log.Debug().Msg("unhandled") } @@ -437,6 +516,9 @@ func (p *Handler) getResourceInfos(ctx context.Context, w http.ResponseWriter, r info := stack[0] stack = stack[1:] + if info.Type != provider.ResourceType_RESOURCE_TYPE_CONTAINER { + continue + } req := &provider.ListContainerRequest{ Ref: &provider.Reference{ ResourceId: spaceInfo.Id, @@ -561,17 +643,14 @@ func MultistatusResponse(ctx context.Context, pf *XML, mds []*provider.ResourceI } responses = append(responses, res) } - responsesXML, err := xml.Marshal(&responses) + + msr := NewMultiStatusResponseXML() + msr.Responses = responses + msg, err := xml.Marshal(msr) if err != nil { return nil, err } - - var buf bytes.Buffer - buf.WriteString(``) - buf.Write(responsesXML) - buf.WriteString(``) - return buf.Bytes(), nil + return msg, nil } // mdToPropResponse converts the CS3 metadata into a webdav PropResponse @@ -1180,11 +1259,6 @@ func quoteEtag(etag string) string { return `"` + strings.Trim(etag, `"`) + `"` } -type countingReader struct { - n int - r io.Reader -} - func (c *countingReader) Read(p []byte) (int, error) { n, err := c.r.Read(p) c.n += n @@ -1200,10 +1274,6 @@ func metadataKeyOf(n *xml.Name) string { } } -// Props represents properties related to a resource -// http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for propfind) -type Props []xml.Name - // UnmarshalXML appends the property names enclosed within start to pn. // // It returns an error if start does not contain any properties or if @@ -1235,37 +1305,3 @@ func (pn *Props) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { } } } - -// XML holds the xml representation of a propfind -// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propfind -type XML struct { - XMLName xml.Name `xml:"DAV: propfind"` - Allprop *struct{} `xml:"DAV: allprop"` - Propname *struct{} `xml:"DAV: propname"` - Prop Props `xml:"DAV: prop"` - Include Props `xml:"DAV: include"` -} - -// ResponseXML holds the xml representation of a propfind response -type ResponseXML struct { - XMLName xml.Name `xml:"d:response"` - Href string `xml:"d:href"` - Propstat []PropstatXML `xml:"d:propstat"` - Status string `xml:"d:status,omitempty"` - Error *errors.ErrorXML `xml:"d:error"` - ResponseDescription string `xml:"d:responsedescription,omitempty"` -} - -// PropstatXML holds the xml representation of a propfind response -// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat -type PropstatXML struct { - // Prop requires DAV: to be the default namespace in the enclosing - // XML. This is due to the standard encoding/xml package currently - // not honoring namespace declarations inside a xmltag with a - // parent element for anonymous slice elements. - // Use of multistatusWriter takes care of this. - Prop []*props.PropertyXML `xml:"d:prop>_ignored_"` - Status string `xml:"d:status"` - Error *errors.ErrorXML `xml:"d:error"` - ResponseDescription string `xml:"d:responsedescription,omitempty"` -} diff --git a/internal/http/services/owncloud/ocdav/propfind/propfind_suite_test.go b/internal/http/services/owncloud/ocdav/propfind/propfind_suite_test.go index d02dba936bd..d7d601b592b 100644 --- a/internal/http/services/owncloud/ocdav/propfind/propfind_suite_test.go +++ b/internal/http/services/owncloud/ocdav/propfind/propfind_suite_test.go @@ -21,7 +21,7 @@ package propfind_test import ( "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/internal/http/services/owncloud/ocdav/propfind/propfind_test.go b/internal/http/services/owncloud/ocdav/propfind/propfind_test.go index 0109fb82a89..5cd80be3cb3 100644 --- a/internal/http/services/owncloud/ocdav/propfind/propfind_test.go +++ b/internal/http/services/owncloud/ocdav/propfind/propfind_test.go @@ -20,17 +20,24 @@ package propfind_test import ( "context" + "encoding/xml" + "io" + "io/ioutil" "net/http" "net/http/httptest" "strings" + link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" sprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/net" "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/propfind" "github.com/cs3org/reva/internal/http/services/owncloud/ocdav/propfind/mocks" "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/stretchr/testify/mock" + "google.golang.org/grpc" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -39,14 +46,236 @@ var _ = Describe("Propfind", func() { handler *propfind.Handler client *mocks.GatewayClient ctx context.Context + + readResponse = func(r io.Reader) (*propfind.MultiStatusResponseUnmarshalXML, string, error) { + buf, err := ioutil.ReadAll(r) + if err != nil { + return nil, "", err + } + res := &propfind.MultiStatusResponseUnmarshalXML{} + err = xml.Unmarshal(buf, res) + if err != nil { + return nil, "", err + } + + return res, string(buf), nil + } + + mockStat = func(ref *sprovider.Reference, info *sprovider.ResourceInfo) { + client.On("Stat", mock.Anything, mock.MatchedBy(func(req *sprovider.StatRequest) bool { + return (ref.ResourceId.GetOpaqueId() == "" || req.Ref.ResourceId.GetOpaqueId() == ref.ResourceId.GetOpaqueId()) && + (ref.Path == "" || req.Ref.Path == ref.Path) + })).Return(&sprovider.StatResponse{ + Status: status.NewOK(ctx), + Info: info, + }, nil) + } + mockListContainer = func(ref *sprovider.Reference, infos []*sprovider.ResourceInfo) { + client.On("ListContainer", mock.Anything, mock.MatchedBy(func(req *sprovider.ListContainerRequest) bool { + match := (ref.ResourceId.GetOpaqueId() == "" || req.Ref.ResourceId.GetOpaqueId() == ref.ResourceId.GetOpaqueId()) && + (ref.Path == "" || req.Ref.Path == ref.Path) + return match + })).Return(&sprovider.ListContainerResponse{ + Status: status.NewOK(ctx), + Infos: infos, + }, nil) + } + + foospace = &sprovider.StorageSpace{ + Opaque: &typesv1beta1.Opaque{ + Map: map[string]*typesv1beta1.OpaqueEntry{ + "path": { + Decoder: "plain", + Value: []byte("/foo"), + }, + }, + }, + Id: &sprovider.StorageSpaceId{OpaqueId: "foospace"}, + Root: &sprovider.ResourceId{OpaqueId: "foospaceroot"}, + Name: "foospace", + } + fooquxspace = &sprovider.StorageSpace{ + Opaque: &typesv1beta1.Opaque{ + Map: map[string]*typesv1beta1.OpaqueEntry{ + "path": { + Decoder: "plain", + Value: []byte("/foo/qux"), + }, + }, + }, + Id: &sprovider.StorageSpaceId{OpaqueId: "fooquxspace"}, + Root: &sprovider.ResourceId{OpaqueId: "fooquxspaceroot"}, + Name: "fooquxspace", + } + fooFileShareSpace = &sprovider.StorageSpace{ + Opaque: &typesv1beta1.Opaque{ + Map: map[string]*typesv1beta1.OpaqueEntry{ + "path": { + Decoder: "plain", + Value: []byte("/foo/Shares/sharedFile"), + }, + }, + }, + Id: &sprovider.StorageSpaceId{OpaqueId: "fooFileShareSpace"}, + Root: &sprovider.ResourceId{OpaqueId: "sharedfile"}, + Name: "fooFileShareSpace", + } + fooFileShare2Space = &sprovider.StorageSpace{ + Opaque: &typesv1beta1.Opaque{ + Map: map[string]*typesv1beta1.OpaqueEntry{ + "path": { + Decoder: "plain", + Value: []byte("/foo/Shares/sharedFile2"), + }, + }, + }, + Id: &sprovider.StorageSpaceId{OpaqueId: "fooFileShareSpace2"}, + Root: &sprovider.ResourceId{OpaqueId: "sharedfile2"}, + Name: "fooFileShareSpace2", + } + fooDirShareSpace = &sprovider.StorageSpace{ + Opaque: &typesv1beta1.Opaque{ + Map: map[string]*typesv1beta1.OpaqueEntry{ + "path": { + Decoder: "plain", + Value: []byte("/foo/Shares/sharedDir"), + }, + }, + }, + Id: &sprovider.StorageSpaceId{OpaqueId: "fooDirShareSpace"}, + Root: &sprovider.ResourceId{OpaqueId: "shareddir"}, + Name: "fooDirShareSpace", + } ) JustBeforeEach(func() { - ctx = context.Background() + ctx = context.WithValue(context.Background(), net.CtxKeyBaseURI, "http://127.0.0.1:3000") client = &mocks.GatewayClient{} handler = propfind.NewHandler("127.0.0.1:3000", func() (propfind.GatewayClient, error) { return client, nil }) + + mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{OpaqueId: "foospaceroot"}, Path: "."}, + &sprovider.ResourceInfo{ + Id: &sprovider.ResourceId{OpaqueId: "foospaceroot", StorageId: "foospaceroot"}, + Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, + Path: ".", + Size: uint64(131), + }) + mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{OpaqueId: "foospaceroot"}, Path: "."}, + []*sprovider.ResourceInfo{ + { + Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, + Path: "bar", + Size: 100, + }, + { + Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, + Path: "baz", + Size: 1, + }, + { + Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, + Path: "dir", + Size: 30, + }, + }) + mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{OpaqueId: "foospaceroot"}, Path: "./bar"}, + &sprovider.ResourceInfo{ + Id: &sprovider.ResourceId{StorageId: "foospace", OpaqueId: "foospacebar"}, + Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, + Path: "./bar", + Size: uint64(100), + }) + mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{OpaqueId: "foospaceroot"}, Path: "./dir"}, + []*sprovider.ResourceInfo{ + { + Id: &sprovider.ResourceId{StorageId: "foospace", OpaqueId: "dirent"}, + Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, + Path: "entry", + Size: 30, + }, + }) + + mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{OpaqueId: "fooquxspaceroot"}, Path: "."}, + &sprovider.ResourceInfo{ + Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, + Path: ".", + Size: uint64(1000), + }) + mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{OpaqueId: "fooquxspaceroot"}, Path: "."}, + []*sprovider.ResourceInfo{ + { + Id: &sprovider.ResourceId{OpaqueId: "fooquxspaceroot", StorageId: "fooquxspaceroot"}, + Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, + Path: "quux", + Size: 1000, + }, + }) + + mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{OpaqueId: "sharedfile"}, Path: "."}, + &sprovider.ResourceInfo{ + Id: &sprovider.ResourceId{OpaqueId: "sharedfile", StorageId: "sharedfile"}, + Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, + Path: ".", + Size: uint64(2000), + Mtime: &typesv1beta1.Timestamp{Seconds: 1}, + Etag: "1", + }) + + mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{OpaqueId: "sharedfile2"}, Path: "."}, + &sprovider.ResourceInfo{ + Id: &sprovider.ResourceId{OpaqueId: "sharedfile2", StorageId: "sharedfile2"}, + Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, + Path: ".", + Size: uint64(2500), + Mtime: &typesv1beta1.Timestamp{Seconds: 2}, + Etag: "2", + }) + mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{OpaqueId: "shareddir"}, Path: "."}, + &sprovider.ResourceInfo{ + Id: &sprovider.ResourceId{OpaqueId: "shareddir", StorageId: "shareddir"}, + Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, + Path: ".", + Size: uint64(1500), + Mtime: &typesv1beta1.Timestamp{Seconds: 3}, + Etag: "3", + }) + mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{OpaqueId: "shareddir"}, Path: "."}, + []*sprovider.ResourceInfo{ + { + Id: &sprovider.ResourceId{OpaqueId: "shareddir", StorageId: "shareddir"}, + Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, + Path: "something", + Size: 1500, + }, + }) + + client.On("ListPublicShares", mock.Anything, mock.Anything).Return( + func(_ context.Context, req *link.ListPublicSharesRequest, _ ...grpc.CallOption) *link.ListPublicSharesResponse { + + var shares []*link.PublicShare + if len(req.Filters) == 0 { + shares = []*link.PublicShare{} + } else { + term := req.Filters[0].Term.(*link.ListPublicSharesRequest_Filter_ResourceId) + switch { + case term != nil && term.ResourceId != nil && term.ResourceId.OpaqueId == "foospacebar": + shares = []*link.PublicShare{ + { + Id: &link.PublicShareId{OpaqueId: "share1"}, + ResourceId: &sprovider.ResourceId{StorageId: "foospace", OpaqueId: "foospacebar"}, + }, + } + default: + shares = []*link.PublicShare{} + } + } + return &link.ListPublicSharesResponse{ + Status: status.NewOK(ctx), + Share: shares, + } + }, nil) }) Describe("NewHandler", func() { @@ -55,7 +284,347 @@ var _ = Describe("Propfind", func() { }) }) + Describe("HandlePathPropfind", func() { + Context("with just one space", func() { + JustBeforeEach(func() { + client.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *sprovider.ListStorageSpacesRequest) bool { + p := string(req.Opaque.Map["path"].Value) + return p == "/" || strings.HasPrefix(p, "/foo") + })).Return(&sprovider.ListStorageSpacesResponse{ + Status: status.NewOK(ctx), + StorageSpaces: []*sprovider.StorageSpace{foospace}, + }, nil) + client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&sprovider.ListStorageSpacesResponse{ + Status: status.NewOK(ctx), + StorageSpaces: []*sprovider.StorageSpace{}, + }, nil) + }) + + It("verifies the depth header", func() { + rr := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/foo", strings.NewReader("")) + req.Header.Set(net.HeaderDepth, "invalid") + req = req.WithContext(ctx) + Expect(err).ToNot(HaveOccurred()) + + handler.HandlePathPropfind(rr, req, "/") + Expect(rr.Code).To(Equal(http.StatusBadRequest)) + }) + + It("stats a path", func() { + rr := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/foo", strings.NewReader("")) + req = req.WithContext(ctx) + Expect(err).ToNot(HaveOccurred()) + + handler.HandlePathPropfind(rr, req, "/") + Expect(rr.Code).To(Equal(http.StatusMultiStatus)) + + res, _, err := readResponse(rr.Result().Body) + Expect(err).ToNot(HaveOccurred()) + Expect(len(res.Responses)).To(Equal(4)) + + root := res.Responses[0] + Expect(root.Href).To(Equal("http:/127.0.0.1:3000/foo/")) + Expect(string(root.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("131")) + + bar := res.Responses[1] + Expect(bar.Href).To(Equal("http:/127.0.0.1:3000/foo/bar")) + Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("100")) + + baz := res.Responses[2] + Expect(baz.Href).To(Equal("http:/127.0.0.1:3000/foo/baz")) + Expect(string(baz.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("1")) + + dir := res.Responses[3] + Expect(dir.Href).To(Equal("http:/127.0.0.1:3000/foo/dir/")) + Expect(string(dir.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("30")) + }) + + It("stats a file", func() { + rr := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/foo/bar", strings.NewReader("")) + req = req.WithContext(ctx) + Expect(err).ToNot(HaveOccurred()) + + handler.HandlePathPropfind(rr, req, "/") + Expect(rr.Code).To(Equal(http.StatusMultiStatus)) + + res, _, err := readResponse(rr.Result().Body) + Expect(err).ToNot(HaveOccurred()) + Expect(len(res.Responses)).To(Equal(1)) + + bar := res.Responses[0] + Expect(bar.Href).To(Equal("http:/127.0.0.1:3000/foo/bar")) + Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("100")) + }) + }) + + Context("with one nested file space", func() { + JustBeforeEach(func() { + client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return( + func(_ context.Context, req *sprovider.ListStorageSpacesRequest, _ ...grpc.CallOption) *sprovider.ListStorageSpacesResponse { + var spaces []*sprovider.StorageSpace + switch string(req.Opaque.Map["path"].Value) { + case "/", "/foo": + spaces = []*sprovider.StorageSpace{foospace, fooFileShareSpace} + case "/foo/Shares", "/foo/Shares/sharedFile": + spaces = []*sprovider.StorageSpace{fooFileShareSpace} + } + return &sprovider.ListStorageSpacesResponse{ + Status: status.NewOK(ctx), + StorageSpaces: spaces, + } + }, + nil) + }) + + It("stats the parent", func() { + rr := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/foo", strings.NewReader("")) + Expect(err).ToNot(HaveOccurred()) + req = req.WithContext(ctx) + + handler.HandlePathPropfind(rr, req, "/") + Expect(rr.Code).To(Equal(http.StatusMultiStatus)) + + res, _, err := readResponse(rr.Result().Body) + Expect(err).ToNot(HaveOccurred()) + Expect(len(res.Responses)).To(Equal(5)) + + parent := res.Responses[0] + Expect(parent.Href).To(Equal("http:/127.0.0.1:3000/foo/")) + Expect(string(parent.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("2131")) + + sf := res.Responses[4] + Expect(sf.Href).To(Equal("http:/127.0.0.1:3000/foo/Shares/")) + Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("2000")) + }) + + It("stats the embedded space", func() { + rr := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/foo/Shares/sharedFile", strings.NewReader("")) + Expect(err).ToNot(HaveOccurred()) + req = req.WithContext(ctx) + + handler.HandlePathPropfind(rr, req, "/") + Expect(rr.Code).To(Equal(http.StatusMultiStatus)) + + res, _, err := readResponse(rr.Result().Body) + Expect(err).ToNot(HaveOccurred()) + Expect(len(res.Responses)).To(Equal(1)) + + sf := res.Responses[0] + Expect(sf.Href).To(Equal("http:/127.0.0.1:3000/foo/Shares/sharedFile")) + Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("2000")) + Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("Thu, 01 Jan 1970 00:00:01 GMT")) + Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring(""1"")) + }) + }) + + Context("with two nested file spaces and a nested directory space", func() { + JustBeforeEach(func() { + client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return( + func(_ context.Context, req *sprovider.ListStorageSpacesRequest, _ ...grpc.CallOption) *sprovider.ListStorageSpacesResponse { + var spaces []*sprovider.StorageSpace + switch string(req.Opaque.Map["path"].Value) { + case "/", "/foo": + spaces = []*sprovider.StorageSpace{foospace, fooFileShareSpace, fooFileShare2Space, fooDirShareSpace} + case "/foo/Shares": + spaces = []*sprovider.StorageSpace{fooFileShareSpace, fooFileShare2Space, fooDirShareSpace} + case "/foo/Shares/sharedFile": + spaces = []*sprovider.StorageSpace{fooFileShareSpace} + case "/foo/Shares/sharedFile2": + spaces = []*sprovider.StorageSpace{fooFileShare2Space} + } + return &sprovider.ListStorageSpacesResponse{ + Status: status.NewOK(ctx), + StorageSpaces: spaces, + } + }, + nil) + }) + + It("stats the parent", func() { + rr := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/foo", strings.NewReader("")) + Expect(err).ToNot(HaveOccurred()) + req = req.WithContext(ctx) + + handler.HandlePathPropfind(rr, req, "/") + Expect(rr.Code).To(Equal(http.StatusMultiStatus)) + + res, _, err := readResponse(rr.Result().Body) + Expect(err).ToNot(HaveOccurred()) + Expect(len(res.Responses)).To(Equal(5)) + + parent := res.Responses[0] + Expect(parent.Href).To(Equal("http:/127.0.0.1:3000/foo/")) + Expect(string(parent.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("6131")) + + shares := res.Responses[4] + Expect(shares.Href).To(Equal("http:/127.0.0.1:3000/foo/Shares/")) + Expect(string(shares.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("6000")) + Expect(string(shares.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("Thu, 01 Jan 1970 00:00:03 GMT")) + Expect(string(shares.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring(""3"")) + }) + + It("stats the embedded space", func() { + rr := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/foo/Shares/sharedFile", strings.NewReader("")) + Expect(err).ToNot(HaveOccurred()) + req = req.WithContext(ctx) + + handler.HandlePathPropfind(rr, req, "/") + Expect(rr.Code).To(Equal(http.StatusMultiStatus)) + + res, _, err := readResponse(rr.Result().Body) + Expect(err).ToNot(HaveOccurred()) + Expect(len(res.Responses)).To(Equal(1)) + + sf := res.Responses[0] + Expect(sf.Href).To(Equal("http:/127.0.0.1:3000/foo/Shares/sharedFile")) + Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("2000")) + }) + + It("includes all the things™ when depth is infinity", func() { + rr := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/foo", strings.NewReader("")) + Expect(err).ToNot(HaveOccurred()) + req = req.WithContext(ctx) + req.Header.Add(net.HeaderDepth, "infinity") + + handler.HandlePathPropfind(rr, req, "/") + Expect(rr.Code).To(Equal(http.StatusMultiStatus)) + + res, _, err := readResponse(rr.Result().Body) + Expect(err).ToNot(HaveOccurred()) + Expect(len(res.Responses)).To(Equal(9)) + + paths := []string{} + for _, r := range res.Responses { + paths = append(paths, r.Href) + } + Expect(paths).To(ConsistOf( + "http:/127.0.0.1:3000/foo/", + "http:/127.0.0.1:3000/foo/bar", + "http:/127.0.0.1:3000/foo/baz", + "http:/127.0.0.1:3000/foo/dir/", + "http:/127.0.0.1:3000/foo/dir/entry", + "http:/127.0.0.1:3000/foo/Shares/sharedFile", + "http:/127.0.0.1:3000/foo/Shares/sharedFile2", + "http:/127.0.0.1:3000/foo/Shares/sharedDir/", + "http:/127.0.0.1:3000/foo/Shares/sharedDir/something", + )) + }) + }) + + Context("with a nested directory space", func() { + JustBeforeEach(func() { + client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return( + func(_ context.Context, req *sprovider.ListStorageSpacesRequest, _ ...grpc.CallOption) *sprovider.ListStorageSpacesResponse { + var spaces []*sprovider.StorageSpace + switch string(req.Opaque.Map["path"].Value) { + case "/", "/foo": + spaces = []*sprovider.StorageSpace{foospace, fooquxspace} + case "/foo/qux": + spaces = []*sprovider.StorageSpace{fooquxspace} + } + return &sprovider.ListStorageSpacesResponse{ + Status: status.NewOK(ctx), + StorageSpaces: spaces, + } + }, + nil) + }) + + // Pending, the code for handling missing parents is still missing + PIt("handles children with no parent", func() { + rr := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/", strings.NewReader("")) + Expect(err).ToNot(HaveOccurred()) + req = req.WithContext(ctx) + + handler.HandlePathPropfind(rr, req, "/") + Expect(rr.Code).To(Equal(http.StatusOK)) + }) + + It("mounts embedded spaces", func() { + rr := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/foo", strings.NewReader("")) + Expect(err).ToNot(HaveOccurred()) + req = req.WithContext(ctx) + + handler.HandlePathPropfind(rr, req, "/") + Expect(rr.Code).To(Equal(http.StatusMultiStatus)) + + res, _, err := readResponse(rr.Result().Body) + Expect(err).ToNot(HaveOccurred()) + Expect(len(res.Responses)).To(Equal(5)) + + root := res.Responses[0] + Expect(root.Href).To(Equal("http:/127.0.0.1:3000/foo/")) + Expect(string(root.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("1131")) + + bar := res.Responses[1] + Expect(bar.Href).To(Equal("http:/127.0.0.1:3000/foo/bar")) + Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("100")) + + baz := res.Responses[2] + Expect(baz.Href).To(Equal("http:/127.0.0.1:3000/foo/baz")) + Expect(string(baz.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("1")) + + dir := res.Responses[3] + Expect(dir.Href).To(Equal("http:/127.0.0.1:3000/foo/dir/")) + Expect(string(dir.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("30")) + + qux := res.Responses[4] + Expect(qux.Href).To(Equal("http:/127.0.0.1:3000/foo/qux/")) + Expect(string(qux.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("1000")) + }) + + It("stats the embedded space", func() { + rr := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/foo/qux/", strings.NewReader("")) + Expect(err).ToNot(HaveOccurred()) + req = req.WithContext(ctx) + + handler.HandlePathPropfind(rr, req, "/") + Expect(rr.Code).To(Equal(http.StatusMultiStatus)) + + res, _, err := readResponse(rr.Result().Body) + Expect(err).ToNot(HaveOccurred()) + Expect(len(res.Responses)).To(Equal(2)) + + qux := res.Responses[0] + Expect(qux.Href).To(Equal("http:/127.0.0.1:3000/foo/qux/")) + Expect(string(qux.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("1000")) + + quux := res.Responses[1] + Expect(quux.Href).To(Equal("http:/127.0.0.1:3000/foo/qux/quux")) + Expect(string(quux.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("1000")) + }) + }) + }) + Describe("HandleSpacesPropfind", func() { + JustBeforeEach(func() { + client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return( + func(_ context.Context, req *sprovider.ListStorageSpacesRequest, _ ...grpc.CallOption) *sprovider.ListStorageSpacesResponse { + var spaces []*sprovider.StorageSpace + switch { + case req.Filters[0].Term.(*sprovider.ListStorageSpacesRequest_Filter_Id).Id.OpaqueId == "foospace": + spaces = []*sprovider.StorageSpace{foospace} + default: + spaces = []*sprovider.StorageSpace{} + } + return &sprovider.ListStorageSpacesResponse{ + Status: status.NewOK(ctx), + StorageSpaces: spaces, + } + }, nil) + }) + It("handles invalid space ids", func() { client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&sprovider.ListStorageSpacesResponse{ Status: status.NewOK(ctx), @@ -66,8 +635,107 @@ var _ = Describe("Propfind", func() { req, err := http.NewRequest("GET", "/", strings.NewReader("")) Expect(err).ToNot(HaveOccurred()) - handler.HandleSpacesPropfind(rr, req, "foo") + handler.HandleSpacesPropfind(rr, req, "does-not-exist") Expect(rr.Code).To(Equal(http.StatusNotFound)) }) + + It("stats the space root", func() { + rr := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/", strings.NewReader("")) + Expect(err).ToNot(HaveOccurred()) + req = req.WithContext(ctx) + + handler.HandleSpacesPropfind(rr, req, "foospace") + Expect(rr.Code).To(Equal(http.StatusMultiStatus)) + + res, _, err := readResponse(rr.Result().Body) + Expect(err).ToNot(HaveOccurred()) + Expect(len(res.Responses)).To(Equal(4)) + + root := res.Responses[0] + Expect(root.Href).To(Equal("http:/127.0.0.1:3000/foospace/")) + Expect(string(root.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("131")) + + bar := res.Responses[1] + Expect(bar.Href).To(Equal("http:/127.0.0.1:3000/foospace/bar")) + Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("100")) + + baz := res.Responses[2] + Expect(baz.Href).To(Equal("http:/127.0.0.1:3000/foospace/baz")) + Expect(string(baz.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("1")) + + dir := res.Responses[3] + Expect(dir.Href).To(Equal("http:/127.0.0.1:3000/foospace/dir/")) + Expect(string(dir.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("30")) + }) + + It("stats a file", func() { + rr := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/bar", strings.NewReader("")) + Expect(err).ToNot(HaveOccurred()) + req = req.WithContext(ctx) + + handler.HandleSpacesPropfind(rr, req, "foospace") + Expect(rr.Code).To(Equal(http.StatusMultiStatus)) + + res, _, err := readResponse(rr.Result().Body) + Expect(err).ToNot(HaveOccurred()) + Expect(len(res.Responses)).To(Equal(1)) + Expect(string(res.Responses[0].Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("100")) + }) + + It("stats a directory", func() { + mockStat(&sprovider.Reference{Path: "./baz"}, &sprovider.ResourceInfo{ + Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, + Size: 50, + }) + mockListContainer(&sprovider.Reference{Path: "./baz"}, []*sprovider.ResourceInfo{ + { + Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, + Size: 50, + }, + }) + + rr := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/baz", strings.NewReader("")) + Expect(err).ToNot(HaveOccurred()) + req = req.WithContext(ctx) + + handler.HandleSpacesPropfind(rr, req, "foospace") + Expect(rr.Code).To(Equal(http.StatusMultiStatus)) + + res, _, err := readResponse(rr.Result().Body) + Expect(err).ToNot(HaveOccurred()) + Expect(len(res.Responses)).To(Equal(2)) + Expect(string(res.Responses[0].Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("50")) + Expect(string(res.Responses[1].Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("50")) + }) + + It("includes all the things™ when depth is infinity", func() { + rr := httptest.NewRecorder() + req, err := http.NewRequest("GET", "/", strings.NewReader("")) + req.Header.Add(net.HeaderDepth, "infinity") + Expect(err).ToNot(HaveOccurred()) + req = req.WithContext(ctx) + + handler.HandleSpacesPropfind(rr, req, "foospace") + Expect(rr.Code).To(Equal(http.StatusMultiStatus)) + + res, _, err := readResponse(rr.Result().Body) + Expect(err).ToNot(HaveOccurred()) + Expect(len(res.Responses)).To(Equal(5)) + + paths := []string{} + for _, r := range res.Responses { + paths = append(paths, r.Href) + } + Expect(paths).To(ConsistOf( + "http:/127.0.0.1:3000/foospace/", + "http:/127.0.0.1:3000/foospace/bar", + "http:/127.0.0.1:3000/foospace/baz", + "http:/127.0.0.1:3000/foospace/dir/", + "http:/127.0.0.1:3000/foospace/dir/entry", + )) + }) }) }) diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending_test.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending_test.go index 8f130da31dc..253370e10fe 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending_test.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/pending_test.go @@ -33,7 +33,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/stretchr/testify/mock" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares_suite_test.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares_suite_test.go index 1b681efb77b..a1ac8abba99 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares_suite_test.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares_suite_test.go @@ -21,7 +21,7 @@ package shares_test import ( "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares_test.go b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares_test.go index 98c723936e0..53ec6744c9b 100644 --- a/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares_test.go +++ b/internal/http/services/owncloud/ocs/handlers/apps/sharing/shares/shares_test.go @@ -36,7 +36,7 @@ import ( "github.com/cs3org/reva/pkg/rgrpc/status" "github.com/stretchr/testify/mock" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/auth/manager/nextcloud/nextcloud_suite_test.go b/pkg/auth/manager/nextcloud/nextcloud_suite_test.go index 7d75b648796..25baa8ffd58 100644 --- a/pkg/auth/manager/nextcloud/nextcloud_suite_test.go +++ b/pkg/auth/manager/nextcloud/nextcloud_suite_test.go @@ -21,7 +21,7 @@ package nextcloud_test import ( "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/auth/manager/nextcloud/nextcloud_test.go b/pkg/auth/manager/nextcloud/nextcloud_test.go index e7f7bdfa846..ce10328843d 100644 --- a/pkg/auth/manager/nextcloud/nextcloud_test.go +++ b/pkg/auth/manager/nextcloud/nextcloud_test.go @@ -34,7 +34,7 @@ import ( ctxpkg "github.com/cs3org/reva/pkg/ctx" jwt "github.com/cs3org/reva/pkg/token/manager/jwt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/auth/manager/owncloudsql/accounts/accounts_suite_test.go b/pkg/auth/manager/owncloudsql/accounts/accounts_suite_test.go index 8564f5a515b..05306baa8c8 100644 --- a/pkg/auth/manager/owncloudsql/accounts/accounts_suite_test.go +++ b/pkg/auth/manager/owncloudsql/accounts/accounts_suite_test.go @@ -21,7 +21,7 @@ package accounts_test import ( "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/auth/manager/owncloudsql/accounts/accounts_test.go b/pkg/auth/manager/owncloudsql/accounts/accounts_test.go index bb00fcbf2f2..02fa613b048 100644 --- a/pkg/auth/manager/owncloudsql/accounts/accounts_test.go +++ b/pkg/auth/manager/owncloudsql/accounts/accounts_test.go @@ -28,7 +28,7 @@ import ( "github.com/cs3org/reva/pkg/auth/manager/owncloudsql/accounts" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/ocm/share/manager/nextcloud/nextcloud_suite_test.go b/pkg/ocm/share/manager/nextcloud/nextcloud_suite_test.go index 7d75b648796..25baa8ffd58 100644 --- a/pkg/ocm/share/manager/nextcloud/nextcloud_suite_test.go +++ b/pkg/ocm/share/manager/nextcloud/nextcloud_suite_test.go @@ -21,7 +21,7 @@ package nextcloud_test import ( "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/ocm/share/manager/nextcloud/nextcloud_test.go b/pkg/ocm/share/manager/nextcloud/nextcloud_test.go index 2f01928ec2b..847cb0f7ff6 100644 --- a/pkg/ocm/share/manager/nextcloud/nextcloud_test.go +++ b/pkg/ocm/share/manager/nextcloud/nextcloud_test.go @@ -36,7 +36,7 @@ import ( "github.com/cs3org/reva/pkg/ocm/share/manager/nextcloud" jwt "github.com/cs3org/reva/pkg/token/manager/jwt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/share/manager/sql/sql_suite_test.go b/pkg/share/manager/sql/sql_suite_test.go index a7e8fec9500..e890f1cb178 100644 --- a/pkg/share/manager/sql/sql_suite_test.go +++ b/pkg/share/manager/sql/sql_suite_test.go @@ -21,7 +21,7 @@ package sql_test import ( "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/share/manager/sql/sql_test.go b/pkg/share/manager/sql/sql_test.go index 4f327ed6ad0..ecc386d2ccd 100644 --- a/pkg/share/manager/sql/sql_test.go +++ b/pkg/share/manager/sql/sql_test.go @@ -38,7 +38,7 @@ import ( _ "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/mock" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/fs/nextcloud/nextcloud_suite_test.go b/pkg/storage/fs/nextcloud/nextcloud_suite_test.go index 7d75b648796..25baa8ffd58 100644 --- a/pkg/storage/fs/nextcloud/nextcloud_suite_test.go +++ b/pkg/storage/fs/nextcloud/nextcloud_suite_test.go @@ -21,7 +21,7 @@ package nextcloud_test import ( "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/fs/nextcloud/nextcloud_test.go b/pkg/storage/fs/nextcloud/nextcloud_test.go index fb081b05923..21c54de795c 100644 --- a/pkg/storage/fs/nextcloud/nextcloud_test.go +++ b/pkg/storage/fs/nextcloud/nextcloud_test.go @@ -37,7 +37,7 @@ import ( "github.com/cs3org/reva/pkg/storage/fs/nextcloud" jwt "github.com/cs3org/reva/pkg/token/manager/jwt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/fs/ocis/blobstore/blobstore_suite_test.go b/pkg/storage/fs/ocis/blobstore/blobstore_suite_test.go index 4add51ce11e..46c475e1155 100644 --- a/pkg/storage/fs/ocis/blobstore/blobstore_suite_test.go +++ b/pkg/storage/fs/ocis/blobstore/blobstore_suite_test.go @@ -21,7 +21,7 @@ package blobstore_test import ( "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/fs/ocis/blobstore/blobstore_test.go b/pkg/storage/fs/ocis/blobstore/blobstore_test.go index 450226a7fa5..fb159dfba59 100644 --- a/pkg/storage/fs/ocis/blobstore/blobstore_test.go +++ b/pkg/storage/fs/ocis/blobstore/blobstore_test.go @@ -27,7 +27,7 @@ import ( "github.com/cs3org/reva/pkg/storage/fs/ocis/blobstore" "github.com/cs3org/reva/tests/helpers" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/fs/ocis/ocis_suite_test.go b/pkg/storage/fs/ocis/ocis_suite_test.go index f42a46046ac..d97da9cf3d3 100644 --- a/pkg/storage/fs/ocis/ocis_suite_test.go +++ b/pkg/storage/fs/ocis/ocis_suite_test.go @@ -21,7 +21,7 @@ package ocis_test import ( "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/fs/ocis/ocis_test.go b/pkg/storage/fs/ocis/ocis_test.go index e2f4e32efcf..b8d1e049a8f 100644 --- a/pkg/storage/fs/ocis/ocis_test.go +++ b/pkg/storage/fs/ocis/ocis_test.go @@ -24,7 +24,7 @@ import ( "github.com/cs3org/reva/pkg/storage/fs/ocis" "github.com/cs3org/reva/tests/helpers" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/fs/owncloudsql/filecache/filecache_suite_test.go b/pkg/storage/fs/owncloudsql/filecache/filecache_suite_test.go index 822a699bca3..b739d704d16 100644 --- a/pkg/storage/fs/owncloudsql/filecache/filecache_suite_test.go +++ b/pkg/storage/fs/owncloudsql/filecache/filecache_suite_test.go @@ -21,7 +21,7 @@ package filecache_test import ( "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/fs/owncloudsql/filecache/filecache_test.go b/pkg/storage/fs/owncloudsql/filecache/filecache_test.go index 4a6eab04381..5c336ca5c20 100644 --- a/pkg/storage/fs/owncloudsql/filecache/filecache_test.go +++ b/pkg/storage/fs/owncloudsql/filecache/filecache_test.go @@ -28,7 +28,7 @@ import ( "github.com/cs3org/reva/pkg/storage/fs/owncloudsql/filecache" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/fs/s3ng/option_test.go b/pkg/storage/fs/s3ng/option_test.go index 05e0cac43bf..d881baa6e99 100644 --- a/pkg/storage/fs/s3ng/option_test.go +++ b/pkg/storage/fs/s3ng/option_test.go @@ -23,7 +23,7 @@ import ( "github.com/cs3org/reva/pkg/storage/fs/s3ng" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/fs/s3ng/s3ng_suite_test.go b/pkg/storage/fs/s3ng/s3ng_suite_test.go index c6df7d972c0..299752cde0f 100644 --- a/pkg/storage/fs/s3ng/s3ng_suite_test.go +++ b/pkg/storage/fs/s3ng/s3ng_suite_test.go @@ -21,7 +21,7 @@ package s3ng_test import ( "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/fs/s3ng/s3ng_test.go b/pkg/storage/fs/s3ng/s3ng_test.go index 697006f8bb2..fc7cadc7e77 100644 --- a/pkg/storage/fs/s3ng/s3ng_test.go +++ b/pkg/storage/fs/s3ng/s3ng_test.go @@ -24,7 +24,7 @@ import ( "github.com/cs3org/reva/pkg/storage/fs/s3ng" "github.com/cs3org/reva/tests/helpers" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/registry/spaces/spaces_suite_test.go b/pkg/storage/registry/spaces/spaces_suite_test.go index 0c51b61eb08..c8b42c2d5c0 100644 --- a/pkg/storage/registry/spaces/spaces_suite_test.go +++ b/pkg/storage/registry/spaces/spaces_suite_test.go @@ -21,7 +21,7 @@ package spaces_test import ( "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/registry/spaces/spaces_test.go b/pkg/storage/registry/spaces/spaces_test.go index a329a2c12f2..0c7f98e7019 100644 --- a/pkg/storage/registry/spaces/spaces_test.go +++ b/pkg/storage/registry/spaces/spaces_test.go @@ -33,7 +33,7 @@ import ( "github.com/stretchr/testify/mock" "google.golang.org/grpc" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/registry/static/static_suite_test.go b/pkg/storage/registry/static/static_suite_test.go index 686ccf33604..6b3cca08ea3 100644 --- a/pkg/storage/registry/static/static_suite_test.go +++ b/pkg/storage/registry/static/static_suite_test.go @@ -21,7 +21,7 @@ package static_test import ( "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/registry/static/static_test.go b/pkg/storage/registry/static/static_test.go index 8fe22a7c5ee..3fb7deab04f 100644 --- a/pkg/storage/registry/static/static_test.go +++ b/pkg/storage/registry/static/static_test.go @@ -27,7 +27,7 @@ import ( ctxpkg "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/storage/registry/static" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/utils/ace/ace_suite_test.go b/pkg/storage/utils/ace/ace_suite_test.go index 62778e0ba39..b7ee0e2a416 100644 --- a/pkg/storage/utils/ace/ace_suite_test.go +++ b/pkg/storage/utils/ace/ace_suite_test.go @@ -21,7 +21,7 @@ package ace_test import ( "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/utils/ace/ace_test.go b/pkg/storage/utils/ace/ace_test.go index 724cbe1c876..a5340ca1b7e 100644 --- a/pkg/storage/utils/ace/ace_test.go +++ b/pkg/storage/utils/ace/ace_test.go @@ -26,7 +26,7 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/pkg/storage/utils/ace" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/utils/decomposedfs/decomposedfs_concurrency_test.go b/pkg/storage/utils/decomposedfs/decomposedfs_concurrency_test.go index 8cae5c42f6e..9f9e7b85354 100644 --- a/pkg/storage/utils/decomposedfs/decomposedfs_concurrency_test.go +++ b/pkg/storage/utils/decomposedfs/decomposedfs_concurrency_test.go @@ -29,7 +29,7 @@ import ( "github.com/stretchr/testify/mock" "github.com/cs3org/reva/tests/helpers" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/utils/decomposedfs/decomposedfs_suite_test.go b/pkg/storage/utils/decomposedfs/decomposedfs_suite_test.go index 9ea8f67dddd..87d937e5571 100644 --- a/pkg/storage/utils/decomposedfs/decomposedfs_suite_test.go +++ b/pkg/storage/utils/decomposedfs/decomposedfs_suite_test.go @@ -21,7 +21,7 @@ package decomposedfs_test import ( "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/utils/decomposedfs/decomposedfs_test.go b/pkg/storage/utils/decomposedfs/decomposedfs_test.go index 0c501723562..b0cd93d8249 100644 --- a/pkg/storage/utils/decomposedfs/decomposedfs_test.go +++ b/pkg/storage/utils/decomposedfs/decomposedfs_test.go @@ -26,7 +26,7 @@ import ( helpers "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/testhelpers" treemocks "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/tree/mocks" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/utils/decomposedfs/grants_test.go b/pkg/storage/utils/decomposedfs/grants_test.go index c063ef4c9cd..4b28c740e02 100644 --- a/pkg/storage/utils/decomposedfs/grants_test.go +++ b/pkg/storage/utils/decomposedfs/grants_test.go @@ -28,7 +28,7 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" helpers "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/testhelpers" "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/xattrs" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/pkg/xattr" "github.com/stretchr/testify/mock" diff --git a/pkg/storage/utils/decomposedfs/lookup_test.go b/pkg/storage/utils/decomposedfs/lookup_test.go index 34ce78efe17..28954453831 100644 --- a/pkg/storage/utils/decomposedfs/lookup_test.go +++ b/pkg/storage/utils/decomposedfs/lookup_test.go @@ -22,7 +22,7 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" helpers "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/testhelpers" "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/xattrs" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/utils/decomposedfs/node/locks_test.go b/pkg/storage/utils/decomposedfs/node/locks_test.go index 4512964113c..d86c677ad29 100644 --- a/pkg/storage/utils/decomposedfs/node/locks_test.go +++ b/pkg/storage/utils/decomposedfs/node/locks_test.go @@ -23,7 +23,7 @@ import ( "os" "github.com/google/uuid" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" diff --git a/pkg/storage/utils/decomposedfs/node/node_suite_test.go b/pkg/storage/utils/decomposedfs/node/node_suite_test.go index 6fd6e84f432..b8b294924a6 100644 --- a/pkg/storage/utils/decomposedfs/node/node_suite_test.go +++ b/pkg/storage/utils/decomposedfs/node/node_suite_test.go @@ -21,7 +21,7 @@ package node_test import ( "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/utils/decomposedfs/node/node_test.go b/pkg/storage/utils/decomposedfs/node/node_test.go index 7c59af7fc13..31c3abebbe8 100644 --- a/pkg/storage/utils/decomposedfs/node/node_test.go +++ b/pkg/storage/utils/decomposedfs/node/node_test.go @@ -27,7 +27,7 @@ import ( "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/node" helpers "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/testhelpers" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/utils/decomposedfs/options/options_suite_test.go b/pkg/storage/utils/decomposedfs/options/options_suite_test.go index 233675759f7..a038d0fe1c9 100644 --- a/pkg/storage/utils/decomposedfs/options/options_suite_test.go +++ b/pkg/storage/utils/decomposedfs/options/options_suite_test.go @@ -21,7 +21,7 @@ package options_test import ( "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/utils/decomposedfs/options/options_test.go b/pkg/storage/utils/decomposedfs/options/options_test.go index a74825c2b65..0ff86610f28 100644 --- a/pkg/storage/utils/decomposedfs/options/options_test.go +++ b/pkg/storage/utils/decomposedfs/options/options_test.go @@ -21,7 +21,7 @@ package options_test import ( "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/options" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/utils/decomposedfs/recycle_test.go b/pkg/storage/utils/decomposedfs/recycle_test.go index a273024b42e..eb71af14a2e 100644 --- a/pkg/storage/utils/decomposedfs/recycle_test.go +++ b/pkg/storage/utils/decomposedfs/recycle_test.go @@ -26,7 +26,7 @@ import ( ctxpkg "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/mocks" helpers "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/testhelpers" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/stretchr/testify/mock" ) diff --git a/pkg/storage/utils/decomposedfs/tree/tree_suite_test.go b/pkg/storage/utils/decomposedfs/tree/tree_suite_test.go index ef20ab4f6e4..53cd4d51525 100644 --- a/pkg/storage/utils/decomposedfs/tree/tree_suite_test.go +++ b/pkg/storage/utils/decomposedfs/tree/tree_suite_test.go @@ -21,7 +21,7 @@ package tree_test import ( "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/utils/decomposedfs/tree/tree_test.go b/pkg/storage/utils/decomposedfs/tree/tree_test.go index 29298a5b661..09d79f072e9 100644 --- a/pkg/storage/utils/decomposedfs/tree/tree_test.go +++ b/pkg/storage/utils/decomposedfs/tree/tree_test.go @@ -31,7 +31,7 @@ import ( "github.com/pkg/xattr" "github.com/stretchr/testify/mock" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/storage/utils/decomposedfs/upload_test.go b/pkg/storage/utils/decomposedfs/upload_test.go index 1db9dc21abd..074cd68e96a 100644 --- a/pkg/storage/utils/decomposedfs/upload_test.go +++ b/pkg/storage/utils/decomposedfs/upload_test.go @@ -42,7 +42,7 @@ import ( "github.com/pkg/xattr" "github.com/stretchr/testify/mock" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/user/manager/nextcloud/nextcloud_suite_test.go b/pkg/user/manager/nextcloud/nextcloud_suite_test.go index 7d75b648796..25baa8ffd58 100644 --- a/pkg/user/manager/nextcloud/nextcloud_suite_test.go +++ b/pkg/user/manager/nextcloud/nextcloud_suite_test.go @@ -21,7 +21,7 @@ package nextcloud_test import ( "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/user/manager/nextcloud/nextcloud_test.go b/pkg/user/manager/nextcloud/nextcloud_test.go index 1ce00082b4c..2e2f9069803 100644 --- a/pkg/user/manager/nextcloud/nextcloud_test.go +++ b/pkg/user/manager/nextcloud/nextcloud_test.go @@ -32,7 +32,7 @@ import ( "github.com/cs3org/reva/pkg/user/manager/nextcloud" "github.com/cs3org/reva/tests/helpers" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/user/manager/owncloudsql/accounts/accounts_suite_test.go b/pkg/user/manager/owncloudsql/accounts/accounts_suite_test.go index 8564f5a515b..05306baa8c8 100644 --- a/pkg/user/manager/owncloudsql/accounts/accounts_suite_test.go +++ b/pkg/user/manager/owncloudsql/accounts/accounts_suite_test.go @@ -21,7 +21,7 @@ package accounts_test import ( "testing" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/pkg/user/manager/owncloudsql/accounts/accounts_test.go b/pkg/user/manager/owncloudsql/accounts/accounts_test.go index 61819b966fa..0b01939389e 100644 --- a/pkg/user/manager/owncloudsql/accounts/accounts_test.go +++ b/pkg/user/manager/owncloudsql/accounts/accounts_test.go @@ -28,7 +28,7 @@ import ( "github.com/cs3org/reva/pkg/user/manager/owncloudsql/accounts" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/tests/integration/grpc/gateway_storageprovider_static_test.go b/tests/integration/grpc/gateway_storageprovider_static_test.go index 7cab4add866..a3881f6bf76 100644 --- a/tests/integration/grpc/gateway_storageprovider_static_test.go +++ b/tests/integration/grpc/gateway_storageprovider_static_test.go @@ -34,7 +34,7 @@ import ( "github.com/cs3org/reva/pkg/rgrpc/todo/pool" jwt "github.com/cs3org/reva/pkg/token/manager/jwt" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/tests/integration/grpc/gateway_storageprovider_test.go b/tests/integration/grpc/gateway_storageprovider_test.go index af7df5ec76a..8265e1c1e86 100644 --- a/tests/integration/grpc/gateway_storageprovider_test.go +++ b/tests/integration/grpc/gateway_storageprovider_test.go @@ -39,7 +39,7 @@ import ( jwt "github.com/cs3org/reva/pkg/token/manager/jwt" "github.com/cs3org/reva/tests/helpers" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/tests/integration/grpc/grpc_suite_test.go b/tests/integration/grpc/grpc_suite_test.go index 8ea909a50fe..042725da660 100644 --- a/tests/integration/grpc/grpc_suite_test.go +++ b/tests/integration/grpc/grpc_suite_test.go @@ -33,7 +33,7 @@ import ( "github.com/google/uuid" "github.com/pkg/errors" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/tests/integration/grpc/storageprovider_test.go b/tests/integration/grpc/storageprovider_test.go index 978486676e7..c7399b32c7d 100644 --- a/tests/integration/grpc/storageprovider_test.go +++ b/tests/integration/grpc/storageprovider_test.go @@ -36,7 +36,7 @@ import ( "github.com/cs3org/reva/tests/helpers" "github.com/google/uuid" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/tests/integration/grpc/userprovider_test.go b/tests/integration/grpc/userprovider_test.go index 1de51e683f3..95a7e520f2b 100644 --- a/tests/integration/grpc/userprovider_test.go +++ b/tests/integration/grpc/userprovider_test.go @@ -29,7 +29,7 @@ import ( jwt "github.com/cs3org/reva/pkg/token/manager/jwt" "google.golang.org/grpc/metadata" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) From 9c927f9fe69b7692e90c4808e8c1cf28dbdb8250 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Thu, 10 Feb 2022 15:04:17 +0100 Subject: [PATCH 44/49] Consolidate all metadata Get's and Set's to central functions. (#2512) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Consolidate all metadata Get's and Set's to central functions. In the decomposed FS, access to xattr was spread all over. This patch consolidates that to use either the Node.SetMetadata() or xattrs.Set(). This allows us to hook in indexing for example. * hound and typos * Add changelog. * Some more fixes to use xattrs functions. * Fix function name in tests. * Fix some linting hints. * Even more linter warning fixes * Even more linting issues * And another iteration * Linter I hate you * Use proper Int formatting Co-authored-by: David Christofas * Update pkg/storage/utils/decomposedfs/node/node.go Co-authored-by: David Christofas * Update pkg/storage/utils/decomposedfs/node/node.go Co-authored-by: David Christofas * Update pkg/storage/utils/decomposedfs/xattrs/xattrs.go Co-authored-by: David Christofas * again linting * use correct variable in decomposedfs Co-authored-by: Jörn Friedrich Dreyer Co-authored-by: David Christofas --- changelog/unreleased/consolidate-xattr.md | 7 + .../utils/decomposedfs/decomposedfs.go | 14 +- pkg/storage/utils/decomposedfs/grants.go | 9 +- pkg/storage/utils/decomposedfs/lookup.go | 5 +- pkg/storage/utils/decomposedfs/node/node.go | 199 +++++++++--------- .../utils/decomposedfs/node/node_test.go | 2 +- pkg/storage/utils/decomposedfs/spaces.go | 16 +- .../utils/decomposedfs/testhelpers/helpers.go | 2 +- pkg/storage/utils/decomposedfs/tree/tree.go | 62 +++--- pkg/storage/utils/decomposedfs/upload.go | 2 +- .../utils/decomposedfs/xattrs/xattrs.go | 53 +++++ 11 files changed, 208 insertions(+), 163 deletions(-) create mode 100644 changelog/unreleased/consolidate-xattr.md diff --git a/changelog/unreleased/consolidate-xattr.md b/changelog/unreleased/consolidate-xattr.md new file mode 100644 index 00000000000..845fe7c1353 --- /dev/null +++ b/changelog/unreleased/consolidate-xattr.md @@ -0,0 +1,7 @@ +Enhancement: Consolidate xattr setter and getter + +- Consolidate all metadata Get's and Set's to central functions. +- Cleaner code by reduction of casts +- Easier to hook functionality like indexing + +https://github.com/cs3org/reva/pull/2512 diff --git a/pkg/storage/utils/decomposedfs/decomposedfs.go b/pkg/storage/utils/decomposedfs/decomposedfs.go index 002baf8ea8a..4dd5b313f79 100644 --- a/pkg/storage/utils/decomposedfs/decomposedfs.go +++ b/pkg/storage/utils/decomposedfs/decomposedfs.go @@ -50,7 +50,6 @@ import ( rtrace "github.com/cs3org/reva/pkg/trace" "github.com/cs3org/reva/pkg/utils" "github.com/pkg/errors" - "github.com/pkg/xattr" "go.opentelemetry.io/otel/codes" ) @@ -234,14 +233,13 @@ func (fs *Decomposedfs) CreateHome(ctx context.Context) (err error) { // update the owner u := ctxpkg.ContextMustGetUser(ctx) - if err = h.WriteMetadata(u.Id); err != nil { + if err = h.WriteAllNodeMetadata(u.Id); err != nil { return } if fs.o.TreeTimeAccounting || fs.o.TreeSizeAccounting { - homePath := h.InternalPath() // mark the home node as the end of propagation - if err = xattr.Set(homePath, xattrs.PropagationAttr, []byte("1")); err != nil { + if err = h.SetMetadata(xattrs.PropagationAttr, "1"); err != nil { appctx.GetLogger(ctx).Error().Err(err).Interface("node", h).Msg("could not mark home as propagation root") return } @@ -340,9 +338,8 @@ func (fs *Decomposedfs) CreateDir(ctx context.Context, ref *provider.Reference) } if fs.o.TreeTimeAccounting || fs.o.TreeSizeAccounting { - nodePath := n.InternalPath() // mark the home node as the end of propagation - if err = xattr.Set(nodePath, xattrs.PropagationAttr, []byte("1")); err != nil { + if err = n.SetMetadata(xattrs.PropagationAttr, "1"); err != nil { appctx.GetLogger(ctx).Error().Err(err).Interface("node", n).Msg("could not mark node to propagate") // FIXME: This does not return an error at all, but results in a severe situation that the @@ -433,12 +430,11 @@ func (fs *Decomposedfs) CreateReference(ctx context.Context, p string, targetURI } childCreated = true - internalPath := childNode.InternalPath() - if err := xattr.Set(internalPath, xattrs.ReferenceAttr, []byte(targetURI.String())); err != nil { + if err := childNode.SetMetadata(xattrs.ReferenceAttr, targetURI.String()); err != nil { // the reference could not be set - that would result in an lost reference? err := errors.Wrapf(err, "Decomposedfs: error setting the target %s on the reference file %s", targetURI.String(), - internalPath, + childNode.InternalPath(), ) span.SetStatus(codes.Error, err.Error()) return err diff --git a/pkg/storage/utils/decomposedfs/grants.go b/pkg/storage/utils/decomposedfs/grants.go index 2920ddd1753..71a11aef1ce 100644 --- a/pkg/storage/utils/decomposedfs/grants.go +++ b/pkg/storage/utils/decomposedfs/grants.go @@ -67,10 +67,9 @@ func (fs *Decomposedfs) AddGrant(ctx context.Context, ref *provider.Reference, g return err } - np := fs.lu.InternalPath(node.ID) e := ace.FromGrant(g) principal, value := e.Marshal() - if err := xattr.Set(np, xattrs.GrantPrefix+principal, value); err != nil { + if err := node.SetMetadata(xattrs.GrantPrefix+principal, string(value)); err != nil { return err } @@ -179,15 +178,15 @@ func extractACEsFromAttrs(ctx context.Context, fsfn string, attrs []string) (ent entries = []*ace.ACE{} for i := range attrs { if strings.HasPrefix(attrs[i], xattrs.GrantPrefix) { - var value []byte + var value string var err error - if value, err = xattr.Get(fsfn, attrs[i]); err != nil { + if value, err = xattrs.Get(fsfn, attrs[i]); err != nil { log.Error().Err(err).Str("attr", attrs[i]).Msg("could not read attribute") continue } var e *ace.ACE principal := attrs[i][len(xattrs.GrantPrefix):] - if e, err = ace.Unmarshal(principal, value); err != nil { + if e, err = ace.Unmarshal(principal, []byte(value)); err != nil { log.Error().Err(err).Str("principal", principal).Str("attr", attrs[i]).Msg("could not unmarshal ace") continue } diff --git a/pkg/storage/utils/decomposedfs/lookup.go b/pkg/storage/utils/decomposedfs/lookup.go index 64c3ee76c09..70a8daa5962 100644 --- a/pkg/storage/utils/decomposedfs/lookup.go +++ b/pkg/storage/utils/decomposedfs/lookup.go @@ -33,7 +33,6 @@ import ( "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/options" "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/xattrs" "github.com/cs3org/reva/pkg/storage/utils/templates" - "github.com/pkg/xattr" ) // Lookup implements transformations from filepath to node and back @@ -148,9 +147,9 @@ func (lu *Lookup) WalkPath(ctx context.Context, r *node.Node, p string, followRe } if followReferences { - if attrBytes, err := xattr.Get(r.InternalPath(), xattrs.ReferenceAttr); err == nil { + if attrBytes, err := r.GetMetadata(xattrs.ReferenceAttr); err == nil { realNodeID := attrBytes - ref, err := xattrs.ReferenceFromAttr(realNodeID) + ref, err := xattrs.ReferenceFromAttr([]byte(realNodeID)) if err != nil { return nil, err } diff --git a/pkg/storage/utils/decomposedfs/node/node.go b/pkg/storage/utils/decomposedfs/node/node.go index f165daaf9cd..6368da6e8e8 100644 --- a/pkg/storage/utils/decomposedfs/node/node.go +++ b/pkg/storage/utils/decomposedfs/node/node.go @@ -110,14 +110,13 @@ func New(id, parentID, name string, blobsize int64, blobID string, owner *userpb func (n *Node) ChangeOwner(new *userpb.UserId) (err error) { nodePath := n.InternalPath() n.owner = new - if err = xattr.Set(nodePath, xattrs.OwnerIDAttr, []byte(new.OpaqueId)); err != nil { - return errors.Wrap(err, "Decomposedfs: could not reset owner id attribute") - } - if err = xattr.Set(nodePath, xattrs.OwnerIDPAttr, []byte(new.Idp)); err != nil { - return errors.Wrap(err, "Decomposedfs: could not reset owner idp attribute") - } - if err = xattr.Set(nodePath, xattrs.OwnerTypeAttr, []byte(utils.UserTypeToString(new.Type))); err != nil { - return errors.Wrap(err, "Decomposedfs: could not reset owner idp attribute") + + var attribs = map[string]string{xattrs.OwnerIDAttr: new.OpaqueId, + xattrs.OwnerIDPAttr: new.Idp, + xattrs.OwnerTypeAttr: utils.UserTypeToString(new.Type)} + + if err := xattrs.SetMultiple(nodePath, attribs); err != nil { + return err } return @@ -127,47 +126,42 @@ func (n *Node) ChangeOwner(new *userpb.UserId) (err error) { // Note that consumers should be aware of the metadata options on xattrs.go. func (n *Node) SetMetadata(key string, val string) (err error) { nodePath := n.InternalPath() - if err := xattr.Set(nodePath, key, []byte(val)); err != nil { - return errors.Wrap(err, "Decomposedfs: could not set parentid attribute") + if err := xattrs.Set(nodePath, key, val); err != nil { + return errors.Wrap(err, "Decomposedfs: could not set extended attribute") } return nil } -// WriteMetadata writes the Node metadata to disk -func (n *Node) WriteMetadata(owner *userpb.UserId) (err error) { +// GetMetadata reads the metadata for the given key +func (n *Node) GetMetadata(key string) (val string, err error) { nodePath := n.InternalPath() - if err = xattr.Set(nodePath, xattrs.ParentidAttr, []byte(n.ParentID)); err != nil { - return errors.Wrap(err, "Decomposedfs: could not set parentid attribute") - } - if err = xattr.Set(nodePath, xattrs.NameAttr, []byte(n.Name)); err != nil { - return errors.Wrap(err, "Decomposedfs: could not set name attribute") - } - if err = xattr.Set(nodePath, xattrs.BlobIDAttr, []byte(n.BlobID)); err != nil { - return errors.Wrap(err, "Decomposedfs: could not set blobid attribute") + if val, err = xattrs.Get(nodePath, key); err != nil { + return "", errors.Wrap(err, "Decomposedfs: could not get extended attribute") } - if err = xattr.Set(nodePath, xattrs.BlobsizeAttr, []byte(fmt.Sprintf("%d", n.Blobsize))); err != nil { - return errors.Wrap(err, "Decomposedfs: could not set blobsize attribute") + return val, nil +} + +// WriteAllNodeMetadata writes the Node metadata to disk +func (n *Node) WriteAllNodeMetadata(owner *userpb.UserId) (err error) { + attribs := make(map[string]string) + + attribs[xattrs.ParentidAttr] = n.ParentID + attribs[xattrs.NameAttr] = n.Name + attribs[xattrs.BlobIDAttr] = n.BlobID + attribs[xattrs.BlobsizeAttr] = strconv.FormatInt(n.Blobsize, 10) + + nodePath := n.InternalPath() + attribs[xattrs.OwnerIDAttr] = "" + attribs[xattrs.OwnerIDPAttr] = "" + attribs[xattrs.OwnerTypeAttr] = "" + + if owner != nil { + attribs[xattrs.OwnerIDAttr] = owner.OpaqueId + attribs[xattrs.OwnerIDPAttr] = owner.Idp + attribs[xattrs.OwnerTypeAttr] = utils.UserTypeToString(owner.Type) } - if owner == nil { - if err = xattr.Set(nodePath, xattrs.OwnerIDAttr, []byte("")); err != nil { - return errors.Wrap(err, "Decomposedfs: could not set empty owner id attribute") - } - if err = xattr.Set(nodePath, xattrs.OwnerIDPAttr, []byte("")); err != nil { - return errors.Wrap(err, "Decomposedfs: could not set empty owner idp attribute") - } - if err = xattr.Set(nodePath, xattrs.OwnerTypeAttr, []byte("")); err != nil { - return errors.Wrap(err, "Decomposedfs: could not set empty owner type attribute") - } - } else { - if err = xattr.Set(nodePath, xattrs.OwnerIDAttr, []byte(owner.OpaqueId)); err != nil { - return errors.Wrap(err, "Decomposedfs: could not set owner id attribute") - } - if err = xattr.Set(nodePath, xattrs.OwnerIDPAttr, []byte(owner.Idp)); err != nil { - return errors.Wrap(err, "Decomposedfs: could not set owner idp attribute") - } - if err = xattr.Set(nodePath, xattrs.OwnerTypeAttr, []byte(utils.UserTypeToString(owner.Type))); err != nil { - return errors.Wrap(err, "Decomposedfs: could not set owner idp attribute") - } + if err := xattrs.SetMultiple(nodePath, attribs); err != nil { + return err } return } @@ -182,11 +176,11 @@ func ReadNode(ctx context.Context, lu PathLookup, id string) (n *Node, err error nodePath := n.InternalPath() // lookup parent id in extended attributes - var attrBytes []byte - attrBytes, err = xattr.Get(nodePath, xattrs.ParentidAttr) + var attr string + attr, err = xattrs.Get(nodePath, xattrs.ParentidAttr) switch { case err == nil: - n.ParentID = string(attrBytes) + n.ParentID = attr case isAttrUnset(err): return nil, errtypes.InternalError(err.Error()) case isNotFound(err): @@ -196,18 +190,18 @@ func ReadNode(ctx context.Context, lu PathLookup, id string) (n *Node, err error } // check if this is a space root - if _, err = xattr.Get(nodePath, xattrs.SpaceNameAttr); err == nil { + if _, err = xattrs.Get(nodePath, xattrs.SpaceNameAttr); err == nil { n.SpaceRoot = n } // lookup name in extended attributes - if attrBytes, err = xattr.Get(nodePath, xattrs.NameAttr); err == nil { - n.Name = string(attrBytes) + if attr, err = xattrs.Get(nodePath, xattrs.NameAttr); err == nil { + n.Name = attr } else { return } // lookup blobID in extended attributes - if attrBytes, err = xattr.Get(nodePath, xattrs.BlobIDAttr); err == nil { - n.BlobID = string(attrBytes) + if attr, err = xattrs.Get(nodePath, xattrs.BlobIDAttr); err == nil { + n.BlobID = attr } else { return } @@ -286,16 +280,14 @@ func (n *Node) Parent() (p *Node, err error) { parentPath := n.lu.InternalPath(n.ParentID) // lookup parent id in extended attributes - var attrBytes []byte - if attrBytes, err = xattr.Get(parentPath, xattrs.ParentidAttr); err == nil { - p.ParentID = string(attrBytes) - } else { + if p.ParentID, err = xattrs.Get(parentPath, xattrs.ParentidAttr); err != nil { + p.ParentID = "" return } // lookup name in extended attributes - if attrBytes, err = xattr.Get(parentPath, xattrs.NameAttr); err == nil { - p.Name = string(attrBytes) - } else { + if p.Name, err = xattrs.Get(parentPath, xattrs.NameAttr); err != nil { + p.Name = "" + p.ParentID = "" return } @@ -321,13 +313,13 @@ func (n *Node) Owner() (*userpb.UserId, error) { // TODO what if this is a reference? nodePath := n.InternalPath() // lookup parent id in extended attributes - var attrBytes []byte + var attr string var err error // lookup ID in extended attributes - attrBytes, err = xattr.Get(nodePath, xattrs.OwnerIDAttr) + attr, err = xattrs.Get(nodePath, xattrs.OwnerIDAttr) switch { case err == nil: - owner.OpaqueId = string(attrBytes) + owner.OpaqueId = attr case isAttrUnset(err), isNotFound(err): fallthrough default: @@ -335,10 +327,10 @@ func (n *Node) Owner() (*userpb.UserId, error) { } // lookup IDP in extended attributes - attrBytes, err = xattr.Get(nodePath, xattrs.OwnerIDPAttr) + attr, err = xattrs.Get(nodePath, xattrs.OwnerIDPAttr) switch { case err == nil: - owner.Idp = string(attrBytes) + owner.Idp = attr case isAttrUnset(err), isNotFound(err): fallthrough default: @@ -346,10 +338,10 @@ func (n *Node) Owner() (*userpb.UserId, error) { } // lookup type in extended attributes - attrBytes, err = xattr.Get(nodePath, xattrs.OwnerTypeAttr) + attr, err = xattrs.Get(nodePath, xattrs.OwnerTypeAttr) switch { case err == nil: - owner.Type = utils.UserTypeMap(string(attrBytes)) + owner.Type = utils.UserTypeMap(attr) case isAttrUnset(err), isNotFound(err): fallthrough default: @@ -458,7 +450,7 @@ func (n *Node) SetEtag(ctx context.Context, val string) (err error) { return nil } // etag is only valid until the calculated etag changes, is part of propagation - return xattr.Set(nodePath, xattrs.TmpEtagAttr, []byte(val)) + return xattrs.Set(nodePath, xattrs.TmpEtagAttr, val) } // SetFavorite sets the favorite for the current user @@ -482,7 +474,7 @@ func (n *Node) SetFavorite(uid *userpb.UserId, val string) error { nodePath := n.lu.InternalPath(n.ID) // the favorite flag is specific to the user, so we need to incorporate the userid fa := fmt.Sprintf("%s:%s:%s@%s", xattrs.FavPrefix, utils.UserTypeToString(uid.GetType()), uid.GetOpaqueId(), uid.GetIdp()) - return xattr.Set(nodePath, fa, []byte(val)) + return xattrs.Set(nodePath, fa, val) } // AsResourceInfo return the node as CS3 ResourceInfo @@ -499,10 +491,10 @@ func (n *Node) AsResourceInfo(ctx context.Context, rp *provider.ResourcePermissi return } - var target []byte + var target string switch { case fi.IsDir(): - if target, err = xattr.Get(nodePath, xattrs.ReferenceAttr); err == nil { + if target, err = xattrs.Get(nodePath, xattrs.ReferenceAttr); err == nil { nodeType = provider.ResourceType_RESOURCE_TYPE_REFERENCE } else { nodeType = provider.ResourceType_RESOURCE_TYPE_CONTAINER @@ -533,7 +525,7 @@ func (n *Node) AsResourceInfo(ctx context.Context, rp *provider.ResourcePermissi Type: nodeType, MimeType: mime.Detect(nodeType == provider.ResourceType_RESOURCE_TYPE_CONTAINER, fn), Size: uint64(n.Blobsize), - Target: string(target), + Target: target, PermissionSet: rp, } @@ -560,8 +552,8 @@ func (n *Node) AsResourceInfo(ctx context.Context, rp *provider.ResourcePermissi } // use temporary etag if it is set - if b, err := xattr.Get(nodePath, xattrs.TmpEtagAttr); err == nil { - ri.Etag = fmt.Sprintf(`"%x"`, string(b)) // TODO why do we convert string(b)? is the temporary etag stored as string? -> should we use bytes? use hex.EncodeToString? + if b, err := xattrs.Get(nodePath, xattrs.TmpEtagAttr); err == nil { + ri.Etag = fmt.Sprintf(`"%x"`, b) // TODO why do we convert string(b)? is the temporary etag stored as string? -> should we use bytes? use hex.EncodeToString? } else if ri.Etag, err = calculateEtag(n.ID, tmTime); err != nil { sublog.Debug().Err(err).Msg("could not calculate etag") } @@ -593,11 +585,11 @@ func (n *Node) AsResourceInfo(ctx context.Context, rp *provider.ResourcePermissi // the favorite flag is specific to the user, so we need to incorporate the userid if uid := u.GetId(); uid != nil { fa := fmt.Sprintf("%s:%s:%s@%s", xattrs.FavPrefix, utils.UserTypeToString(uid.GetType()), uid.GetOpaqueId(), uid.GetIdp()) - if val, err := xattr.Get(nodePath, fa); err == nil { + if val, err := xattrs.Get(nodePath, fa); err == nil { sublog.Debug(). Str("favorite", fa). Msg("found favorite flag") - favorite = string(val) + favorite = val } } else { sublog.Error().Err(errtypes.UserRequired("userrequired")).Msg("user has no id") @@ -659,8 +651,8 @@ func (n *Node) AsResourceInfo(ctx context.Context, rp *provider.ResourcePermissi // only read when key was requested k := attrs[i][len(xattrs.MetadataPrefix):] if _, ok := mdKeysMap[k]; returnAllKeys || ok { - if val, err := xattr.Get(nodePath, attrs[i]); err == nil { - metadata[k] = string(val) + if val, err := xattrs.Get(nodePath, attrs[i]); err == nil { + metadata[k] = val } else { sublog.Error().Err(err). Str("entry", attrs[i]). @@ -682,12 +674,12 @@ func (n *Node) AsResourceInfo(ctx context.Context, rp *provider.ResourcePermissi } func readChecksumIntoResourceChecksum(ctx context.Context, nodePath, algo string, ri *provider.ResourceInfo) { - v, err := xattr.Get(nodePath, xattrs.ChecksumPrefix+algo) + v, err := xattrs.Get(nodePath, xattrs.ChecksumPrefix+algo) switch { case err == nil: ri.Checksum = &provider.ResourceChecksum{ Type: storageprovider.PKG2GRPCXS(algo), - Sum: hex.EncodeToString(v), + Sum: hex.EncodeToString([]byte(v)), } case isAttrUnset(err): appctx.GetLogger(ctx).Debug().Err(err).Str("nodepath", nodePath).Str("algorithm", algo).Msg("checksum not set") @@ -699,7 +691,7 @@ func readChecksumIntoResourceChecksum(ctx context.Context, nodePath, algo string } func readChecksumIntoOpaque(ctx context.Context, nodePath, algo string, ri *provider.ResourceInfo) { - v, err := xattr.Get(nodePath, xattrs.ChecksumPrefix+algo) + v, err := xattrs.Get(nodePath, xattrs.ChecksumPrefix+algo) switch { case err == nil: if ri.Opaque == nil { @@ -709,7 +701,7 @@ func readChecksumIntoOpaque(ctx context.Context, nodePath, algo string, ri *prov } ri.Opaque.Map[algo] = &types.OpaqueEntry{ Decoder: "plain", - Value: []byte(hex.EncodeToString(v)), + Value: []byte(hex.EncodeToString([]byte(v))), } case isAttrUnset(err): appctx.GetLogger(ctx).Debug().Err(err).Str("nodepath", nodePath).Str("algorithm", algo).Msg("checksum not set") @@ -722,7 +714,7 @@ func readChecksumIntoOpaque(ctx context.Context, nodePath, algo string, ri *prov // quota is always stored on the root node func readQuotaIntoOpaque(ctx context.Context, nodePath string, ri *provider.ResourceInfo) { - v, err := xattr.Get(nodePath, xattrs.QuotaAttr) + v, err := xattrs.Get(nodePath, xattrs.QuotaAttr) switch { case err == nil: // make sure we have a proper signed int @@ -730,7 +722,7 @@ func readQuotaIntoOpaque(ctx context.Context, nodePath string, ri *provider.Reso // -1 = uncalculated // -2 = unknown // -3 = unlimited - if _, err := strconv.ParseInt(string(v), 10, 64); err == nil { + if _, err := strconv.ParseInt(v, 10, 64); err == nil { if ri.Opaque == nil { ri.Opaque = &types.Opaque{ Map: map[string]*types.OpaqueEntry{}, @@ -738,10 +730,10 @@ func readQuotaIntoOpaque(ctx context.Context, nodePath string, ri *provider.Reso } ri.Opaque.Map[QuotaKey] = &types.OpaqueEntry{ Decoder: "plain", - Value: v, + Value: []byte(v), } } else { - appctx.GetLogger(ctx).Error().Err(err).Str("nodepath", nodePath).Str("quota", string(v)).Msg("malformed quota") + appctx.GetLogger(ctx).Error().Err(err).Str("nodepath", nodePath).Str("quota", v).Msg("malformed quota") } case isAttrUnset(err): appctx.GetLogger(ctx).Debug().Err(err).Str("nodepath", nodePath).Msg("quota not set") @@ -754,43 +746,43 @@ func readQuotaIntoOpaque(ctx context.Context, nodePath string, ri *provider.Reso // HasPropagation checks if the propagation attribute exists and is set to "1" func (n *Node) HasPropagation() (propagation bool) { - if b, err := xattr.Get(n.lu.InternalPath(n.ID), xattrs.PropagationAttr); err == nil { - return string(b) == "1" + if b, err := xattrs.Get(n.lu.InternalPath(n.ID), xattrs.PropagationAttr); err == nil { + return b == "1" } return false } // GetTMTime reads the tmtime from the extended attributes func (n *Node) GetTMTime() (tmTime time.Time, err error) { - var b []byte - if b, err = xattr.Get(n.lu.InternalPath(n.ID), xattrs.TreeMTimeAttr); err != nil { + var b string + if b, err = xattrs.Get(n.lu.InternalPath(n.ID), xattrs.TreeMTimeAttr); err != nil { return } - return time.Parse(time.RFC3339Nano, string(b)) + return time.Parse(time.RFC3339Nano, b) } // SetTMTime writes the tmtime to the extended attributes func (n *Node) SetTMTime(t time.Time) (err error) { - return xattr.Set(n.lu.InternalPath(n.ID), xattrs.TreeMTimeAttr, []byte(t.UTC().Format(time.RFC3339Nano))) + return xattrs.Set(n.lu.InternalPath(n.ID), xattrs.TreeMTimeAttr, t.UTC().Format(time.RFC3339Nano)) } // GetTreeSize reads the treesize from the extended attributes func (n *Node) GetTreeSize() (treesize uint64, err error) { - var b []byte - if b, err = xattr.Get(n.InternalPath(), xattrs.TreesizeAttr); err != nil { + var b string + if b, err = xattrs.Get(n.InternalPath(), xattrs.TreesizeAttr); err != nil { return } - return strconv.ParseUint(string(b), 10, 64) + return strconv.ParseUint(b, 10, 64) } // SetTreeSize writes the treesize to the extended attributes func (n *Node) SetTreeSize(ts uint64) (err error) { - return xattr.Set(n.InternalPath(), xattrs.TreesizeAttr, []byte(strconv.FormatUint(ts, 10))) + return n.SetMetadata(xattrs.TreesizeAttr, strconv.FormatUint(ts, 10)) } // SetChecksum writes the checksum with the given checksum type to the extended attributes func (n *Node) SetChecksum(csType string, h hash.Hash) (err error) { - return xattr.Set(n.lu.InternalPath(n.ID), xattrs.ChecksumPrefix+csType, h.Sum(nil)) + return n.SetMetadata(xattrs.ChecksumPrefix+csType, string(h.Sum(nil))) } // UnsetTempEtag removes the temporary etag attribute @@ -889,6 +881,7 @@ func (n *Node) ReadUserPermissions(ctx context.Context, u *userpb.User) (ap prov // The function will return a list of opaque strings that can be used to make a ReadGrant call func (n *Node) ListGrantees(ctx context.Context) (grantees []string, err error) { var attrs []string + if attrs, err = xattr.List(n.InternalPath()); err != nil { appctx.GetLogger(ctx).Error().Err(err).Str("node", n.ID).Msg("error listing attributes") return nil, err @@ -903,12 +896,12 @@ func (n *Node) ListGrantees(ctx context.Context) (grantees []string, err error) // ReadGrant reads a CS3 grant func (n *Node) ReadGrant(ctx context.Context, grantee string) (g *provider.Grant, err error) { - var b []byte - if b, err = xattr.Get(n.InternalPath(), grantee); err != nil { + var b string + if b, err = xattrs.Get(n.InternalPath(), grantee); err != nil { return nil, err } var e *ace.ACE - if e, err = ace.Unmarshal(strings.TrimPrefix(grantee, xattrs.GrantPrefix), b); err != nil { + if e, err = ace.Unmarshal(strings.TrimPrefix(grantee, xattrs.GrantPrefix), []byte(b)); err != nil { return nil, err } return e.Grant(), nil @@ -940,11 +933,11 @@ func (n *Node) ListGrants(ctx context.Context) ([]*provider.Grant, error) { // ReadBlobSizeAttr reads the blobsize from the xattrs func ReadBlobSizeAttr(path string) (int64, error) { - attrBytes, err := xattr.Get(path, xattrs.BlobsizeAttr) + attr, err := xattrs.Get(path, xattrs.BlobsizeAttr) if err != nil { return 0, errors.Wrapf(err, "error reading blobsize xattr") } - blobSize, err := strconv.ParseInt(string(attrBytes), 10, 64) + blobSize, err := strconv.ParseInt(attr, 10, 64) if err != nil { return 0, errors.Wrapf(err, "invalid blobsize xattr format") } @@ -1001,7 +994,7 @@ func (n *Node) FindStorageSpaceRoot() error { // IsSpaceRoot checks if the node is a space root func IsSpaceRoot(r *Node) bool { path := r.InternalPath() - if _, err := xattr.Get(path, xattrs.SpaceNameAttr); err == nil { + if _, err := xattrs.Get(path, xattrs.SpaceNameAttr); err == nil { return true } return false @@ -1013,13 +1006,13 @@ var CheckQuota = func(spaceRoot *Node, fileSize uint64) (quotaSufficient bool, e if !enoughDiskSpace(spaceRoot.InternalPath(), fileSize) { return false, errtypes.InsufficientStorage("disk full") } - quotaByte, _ := xattr.Get(spaceRoot.InternalPath(), xattrs.QuotaAttr) + quotaByte, _ := xattrs.Get(spaceRoot.InternalPath(), xattrs.QuotaAttr) var total uint64 - if quotaByte == nil { + if quotaByte == "" { // if quota is not set, it means unlimited return true, nil } - total, _ = strconv.ParseUint(string(quotaByte), 10, 64) + total, _ = strconv.ParseUint(quotaByte, 10, 64) // if total is smaller than used, total-used could overflow and be bigger than fileSize if fileSize > total-used || total < used { return false, errtypes.InsufficientStorage("quota exceeded") diff --git a/pkg/storage/utils/decomposedfs/node/node_test.go b/pkg/storage/utils/decomposedfs/node/node_test.go index 31c3abebbe8..d1db6202470 100644 --- a/pkg/storage/utils/decomposedfs/node/node_test.go +++ b/pkg/storage/utils/decomposedfs/node/node_test.go @@ -97,7 +97,7 @@ var _ = Describe("Node", func() { Type: userpb.UserType_USER_TYPE_PRIMARY, } - err = n.WriteMetadata(owner) + err = n.WriteAllNodeMetadata(owner) Expect(err).ToNot(HaveOccurred()) n2, err := env.Lookup.NodeFromResource(env.Ctx, ref) Expect(err).ToNot(HaveOccurred()) diff --git a/pkg/storage/utils/decomposedfs/spaces.go b/pkg/storage/utils/decomposedfs/spaces.go index d3b7c98c6ec..957cd3ce2d4 100644 --- a/pkg/storage/utils/decomposedfs/spaces.go +++ b/pkg/storage/utils/decomposedfs/spaces.go @@ -44,7 +44,6 @@ import ( "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/xattrs" "github.com/cs3org/reva/pkg/utils" "github.com/google/uuid" - "github.com/pkg/xattr" ) const ( @@ -92,9 +91,8 @@ func (fs *Decomposedfs) CreateStorageSpace(ctx context.Context, req *provider.Cr } // always enable propagation on the storage space root - nodePath := n.InternalPath() // mark the space root node as the end of propagation - if err = xattr.Set(nodePath, xattrs.PropagationAttr, []byte("1")); err != nil { + if err = n.SetMetadata(xattrs.PropagationAttr, "1"); err != nil { appctx.GetLogger(ctx).Error().Err(err).Interface("node", n).Msg("could not mark node to propagate") return nil, err } @@ -543,10 +541,10 @@ func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, } // TODO apply more filters - - sname := "" - if bytes, err := xattr.Get(n.InternalPath(), xattrs.SpaceNameAttr); err == nil { - sname = string(bytes) + var sname string + if sname, err = n.GetMetadata(xattrs.SpaceNameAttr); err != nil { + // FIXME: Is that a severe problem? + appctx.GetLogger(ctx).Debug().Err(err).Msg("space does not have a name attribute") } if err := n.FindStorageSpaceRoot(); err != nil { @@ -637,14 +635,14 @@ func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, } // quota - v, err := xattr.Get(nodePath, xattrs.QuotaAttr) + v, err := xattrs.Get(nodePath, xattrs.QuotaAttr) if err == nil { // make sure we have a proper signed int // we use the same magic numbers to indicate: // -1 = uncalculated // -2 = unknown // -3 = unlimited - if quota, err := strconv.ParseUint(string(v), 10, 64); err == nil { + if quota, err := strconv.ParseUint(v, 10, 64); err == nil { space.Quota = &provider.Quota{ QuotaMaxBytes: quota, QuotaMaxFiles: math.MaxUint64, // TODO MaxUInt64? = unlimited? why even max files? 0 = unlimited? diff --git a/pkg/storage/utils/decomposedfs/testhelpers/helpers.go b/pkg/storage/utils/decomposedfs/testhelpers/helpers.go index dc2194bb97b..102647811aa 100644 --- a/pkg/storage/utils/decomposedfs/testhelpers/helpers.go +++ b/pkg/storage/utils/decomposedfs/testhelpers/helpers.go @@ -151,7 +151,7 @@ func (t *TestEnv) CreateTestFile(name, blobID string, blobSize int64, parentID s if err != nil { return nil, err } - err = file.WriteMetadata(t.Owner.Id) + err = file.WriteAllNodeMetadata(t.Owner.Id) if err != nil { return nil, err } diff --git a/pkg/storage/utils/decomposedfs/tree/tree.go b/pkg/storage/utils/decomposedfs/tree/tree.go index 1fb125ea2b3..ef94fe30c3d 100644 --- a/pkg/storage/utils/decomposedfs/tree/tree.go +++ b/pkg/storage/utils/decomposedfs/tree/tree.go @@ -118,11 +118,11 @@ func (t *Tree) Setup(owner *userpb.UserId, propagateToRoot bool) error { } // set propagation flag - v := []byte("0") + v := "0" if propagateToRoot { - v = []byte("1") + v = "1" } - if err = xattr.Set(n.InternalPath(), xattrs.PropagationAttr, v); err != nil { + if err = n.SetMetadata(xattrs.PropagationAttr, v); err != nil { return err } @@ -206,8 +206,8 @@ func (t *Tree) linkSpace(spaceType, spaceID, nodeID string) { } func isRootNode(nodePath string) bool { - attrBytes, err := xattr.Get(nodePath, xattrs.ParentidAttr) - return err == nil && string(attrBytes) == "root" + attr, err := xattrs.Get(nodePath, xattrs.ParentidAttr) + return err == nil && attr == "root" } func isSharedNode(nodePath string) bool { if attrs, err := xattr.List(nodePath); err == nil { @@ -310,7 +310,7 @@ func (t *Tree) Move(ctx context.Context, oldNode *node.Node, newNode *node.Node) } // update name attribute - if err := xattr.Set(tgtPath, xattrs.NameAttr, []byte(newNode.Name)); err != nil { + if err := xattrs.Set(tgtPath, xattrs.NameAttr, newNode.Name); err != nil { return errors.Wrap(err, "Decomposedfs: could not set name attribute") } @@ -330,10 +330,10 @@ func (t *Tree) Move(ctx context.Context, oldNode *node.Node, newNode *node.Node) } // update target parentid and name - if err := xattr.Set(tgtPath, xattrs.ParentidAttr, []byte(newNode.ParentID)); err != nil { + if err := xattrs.Set(tgtPath, xattrs.ParentidAttr, newNode.ParentID); err != nil { return errors.Wrap(err, "Decomposedfs: could not set parentid attribute") } - if err := xattr.Set(tgtPath, xattrs.NameAttr, []byte(newNode.Name)); err != nil { + if err := xattrs.Set(tgtPath, xattrs.NameAttr, newNode.Name); err != nil { return errors.Wrap(err, "Decomposedfs: could not set name attribute") } @@ -411,7 +411,7 @@ func (t *Tree) Delete(ctx context.Context, n *node.Node) (err error) { // set origin location in metadata nodePath := n.InternalPath() - if err := xattr.Set(nodePath, xattrs.TrashOriginAttr, []byte(origin)); err != nil { + if err := n.SetMetadata(xattrs.TrashOriginAttr, origin); err != nil { return err } @@ -523,13 +523,13 @@ func (t *Tree) RestoreRecycleItemFunc(ctx context.Context, spaceid, key, trashPa targetNode.Exists = true // update name attribute - if err := xattr.Set(nodePath, xattrs.NameAttr, []byte(targetNode.Name)); err != nil { + if err := recycleNode.SetMetadata(xattrs.NameAttr, targetNode.Name); err != nil { return errors.Wrap(err, "Decomposedfs: could not set name attribute") } // set ParentidAttr to restorePath's node parent id if trashPath != "" { - if err := xattr.Set(nodePath, xattrs.ParentidAttr, []byte(targetNode.ParentID)); err != nil { + if err := recycleNode.SetMetadata(xattrs.ParentidAttr, targetNode.ParentID); err != nil { return errors.Wrap(err, "Decomposedfs: could not set name attribute") } } @@ -734,13 +734,13 @@ func calculateTreeSize(ctx context.Context, nodePath string) (uint64, error) { size += uint64(blobSize) } else { // read from attr - var b []byte - // xattr.Get will follow the symlink - if b, err = xattr.Get(cPath, xattrs.TreesizeAttr); err != nil { + var b string + // xattrs.Get will follow the symlink + if b, err = xattrs.Get(cPath, xattrs.TreesizeAttr); err != nil { // TODO recursively descend and recalculate treesize continue // continue after an error } - csize, err := strconv.ParseUint(string(b), 10, 64) + csize, err := strconv.ParseUint(b, 10, 64) if err != nil { // TODO recursively descend and recalculate treesize continue // continue after an error @@ -779,7 +779,7 @@ func (t *Tree) createNode(n *node.Node, owner *userpb.UserId) (err error) { return errors.Wrap(err, "Decomposedfs: error creating node") } - return n.WriteMetadata(owner) + return n.WriteAllNodeMetadata(owner) } // TODO refactor the returned params into Node properties? would make all the path transformations go away... @@ -797,48 +797,48 @@ func (t *Tree) readRecycleItem(ctx context.Context, spaceid, key, path string) ( return } - var attrBytes []byte + var attrStr string trashNodeID := filepath.Base(link) deletedNodePath = t.lookup.InternalPath(trashNodeID) owner := &userpb.UserId{} // lookup ownerId in extended attributes - if attrBytes, err = xattr.Get(deletedNodePath, xattrs.OwnerIDAttr); err == nil { - owner.OpaqueId = string(attrBytes) + if attrStr, err = xattrs.Get(deletedNodePath, xattrs.OwnerIDAttr); err == nil { + owner.OpaqueId = attrStr } else { return } // lookup ownerIdp in extended attributes - if attrBytes, err = xattr.Get(deletedNodePath, xattrs.OwnerIDPAttr); err == nil { - owner.Idp = string(attrBytes) + if attrStr, err = xattrs.Get(deletedNodePath, xattrs.OwnerIDPAttr); err == nil { + owner.Idp = attrStr } else { return } // lookup ownerType in extended attributes - if attrBytes, err = xattr.Get(deletedNodePath, xattrs.OwnerTypeAttr); err == nil { - owner.Type = utils.UserTypeMap(string(attrBytes)) + if attrStr, err = xattrs.Get(deletedNodePath, xattrs.OwnerTypeAttr); err == nil { + owner.Type = utils.UserTypeMap(attrStr) } else { return } recycleNode = node.New(trashNodeID, "", "", 0, "", owner, t.lookup) // lookup blobID in extended attributes - if attrBytes, err = xattr.Get(deletedNodePath, xattrs.BlobIDAttr); err == nil { - recycleNode.BlobID = string(attrBytes) + if attrStr, err = xattrs.Get(deletedNodePath, xattrs.BlobIDAttr); err == nil { + recycleNode.BlobID = attrStr } else { return } // lookup parent id in extended attributes - if attrBytes, err = xattr.Get(deletedNodePath, xattrs.ParentidAttr); err == nil { - recycleNode.ParentID = string(attrBytes) + if attrStr, err = xattrs.Get(deletedNodePath, xattrs.ParentidAttr); err == nil { + recycleNode.ParentID = attrStr } else { return } // lookup name in extended attributes - if attrBytes, err = xattr.Get(deletedNodePath, xattrs.NameAttr); err == nil { - recycleNode.Name = string(attrBytes) + if attrStr, err = xattrs.Get(deletedNodePath, xattrs.NameAttr); err == nil { + recycleNode.Name = attrStr } else { return } @@ -871,8 +871,8 @@ func (t *Tree) readRecycleItem(ctx context.Context, spaceid, key, path string) ( deletedNodeRootPath = t.lookup.InternalPath(filepath.Base(rootLink)) } // lookup origin path in extended attributes - if attrBytes, err = xattr.Get(deletedNodeRootPath, xattrs.TrashOriginAttr); err == nil { - origin = filepath.Join(string(attrBytes), path) + if attrStr, err = xattrs.Get(deletedNodeRootPath, xattrs.TrashOriginAttr); err == nil { + origin = filepath.Join(attrStr, path) } else { log.Error().Err(err).Str("trashItem", trashItem).Str("link", link).Str("deletedNodePath", deletedNodePath).Msg("could not read origin path, restoring to /") } diff --git a/pkg/storage/utils/decomposedfs/upload.go b/pkg/storage/utils/decomposedfs/upload.go index 3a4a4e17b49..895b590d35a 100644 --- a/pkg/storage/utils/decomposedfs/upload.go +++ b/pkg/storage/utils/decomposedfs/upload.go @@ -602,7 +602,7 @@ func (upload *fileUpload) FinishUpload(ctx context.Context) (err error) { tryWritingChecksum(&sublog, n, "adler32", adler32h) // who will become the owner? the owner of the parent actually ... not the currently logged in user - err = n.WriteMetadata(&userpb.UserId{ + err = n.WriteAllNodeMetadata(&userpb.UserId{ Idp: upload.info.Storage["OwnerIdp"], OpaqueId: upload.info.Storage["OwnerId"], }) diff --git a/pkg/storage/utils/decomposedfs/xattrs/xattrs.go b/pkg/storage/utils/decomposedfs/xattrs/xattrs.go index 9688e042fc2..f65900c202a 100644 --- a/pkg/storage/utils/decomposedfs/xattrs/xattrs.go +++ b/pkg/storage/utils/decomposedfs/xattrs/xattrs.go @@ -22,6 +22,7 @@ import ( "strings" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/pkg/errors" "github.com/pkg/xattr" ) @@ -127,3 +128,55 @@ func CopyMetadata(s, t string, filter func(attributeName string) bool) error { } return nil } + +// Set an extended attribute key to the given value +func Set(filePath string, key string, val string) error { + + if err := xattr.Set(filePath, key, []byte(val)); err != nil { + return errors.Wrap(err, "xattrs: Could not write xtended attribute") + } + return nil +} + +// SetMultiple allows setting multiple key value pairs at once +// TODO the changes are protected with an flock +func SetMultiple(filePath string, attribs map[string]string) error { + + // FIXME: Lock here + for key, val := range attribs { + if err := Set(filePath, key, val); err != nil { + return err + } + } + return nil +} + +// Get an extended attribute value for the given key +func Get(filePath, key string) (string, error) { + + v, err := xattr.Get(filePath, key) + if err != nil { + return "", errors.Wrap(err, "xattrs: Can not read xattr") + } + val := string(v) + return val, nil +} + +// All reads all extended attributes for a node +func All(filePath string) (map[string]string, error) { + attrNames, err := xattr.List(filePath) + if err != nil { + return nil, errors.Wrap(err, "xattrs: Can not list extended attributes") + } + + attribs := make(map[string]string, len(attrNames)) + for _, name := range attrNames { + val, err := xattr.Get(filePath, name) + if err != nil { + return nil, errors.Wrap(err, "Failed to read extended attrib") + } + attribs[name] = string(val) + } + + return attribs, nil +} From 4673e72c8799d63ddb8f0d5550d8f11686696d57 Mon Sep 17 00:00:00 2001 From: kobergj Date: Fri, 11 Feb 2022 10:31:52 +0100 Subject: [PATCH 45/49] Events (#2522) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * first draft for event system - includes example Signed-off-by: jkoberg * add event middleware Signed-off-by: jkoberg * events: distinguish grantee userid and groupid Signed-off-by: Jörn Friedrich Dreyer * seperate consumer from publisher Signed-off-by: jkoberg * code review suggestions Signed-off-by: jkoberg * simplify example Signed-off-by: jkoberg * add changelog Signed-off-by: jkoberg * make nats server configurable Signed-off-by: jkoberg * add license headers Signed-off-by: jkoberg * cheat the linter Signed-off-by: jkoberg Co-authored-by: Jörn Friedrich Dreyer --- changelog/unreleased/events.md | 7 + go.mod | 10 +- go.sum | 430 +++++++++++++++++- .../eventsmiddleware/conversion.go | 37 ++ .../interceptors/eventsmiddleware/events.go | 95 ++++ internal/grpc/interceptors/loader/loader.go | 1 + pkg/events/events.go | 107 +++++ pkg/events/example/consumer/consumer.go | 60 +++ pkg/events/example/example.go | 70 +++ pkg/events/example/publisher/publisher.go | 44 ++ pkg/events/server/nats.go | 43 ++ pkg/events/server/options.go | 55 +++ pkg/events/types.go | 46 ++ 13 files changed, 995 insertions(+), 10 deletions(-) create mode 100644 changelog/unreleased/events.md create mode 100644 internal/grpc/interceptors/eventsmiddleware/conversion.go create mode 100644 internal/grpc/interceptors/eventsmiddleware/events.go create mode 100644 pkg/events/events.go create mode 100644 pkg/events/example/consumer/consumer.go create mode 100644 pkg/events/example/example.go create mode 100644 pkg/events/example/publisher/publisher.go create mode 100644 pkg/events/server/nats.go create mode 100644 pkg/events/server/options.go create mode 100644 pkg/events/types.go diff --git a/changelog/unreleased/events.md b/changelog/unreleased/events.md new file mode 100644 index 00000000000..b96bc2b22d1 --- /dev/null +++ b/changelog/unreleased/events.md @@ -0,0 +1,7 @@ +Enhancement: introduce events + +This will introduce events into the system. Events are a simple way to bring information from +one service to another. Read `pkg/events/example` and subfolders for more information + +https://github.com/cs3org/reva/pull/2522 + diff --git a/go.mod b/go.mod index 682a62c9460..e61ef61a4d3 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/Masterminds/sprig v2.22.0+incompatible github.com/ReneKroon/ttlcache/v2 v2.11.0 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect + github.com/asim/go-micro/plugins/events/nats/v4 v4.0.0-20220118152736-9e0be6c85d75 github.com/aws/aws-sdk-go v1.42.39 github.com/beevik/etree v1.1.0 github.com/bluele/gcache v0.0.2 @@ -38,7 +39,6 @@ require ( github.com/hashicorp/go-hclog v1.1.0 github.com/hashicorp/go-plugin v1.4.3 github.com/huandu/xstrings v1.3.2 // indirect - github.com/imdario/mergo v0.3.12 // indirect github.com/jedib0t/go-pretty v4.3.0+incompatible github.com/juliangruber/go-intersect v1.1.0 github.com/mattn/go-sqlite3 v1.14.10 @@ -48,6 +48,8 @@ require ( github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/mapstructure v1.4.3 + github.com/nats-io/nats-server/v2 v2.7.2 + github.com/nats-io/nats-streaming-server v0.24.1 github.com/onsi/ginkgo/v2 v2.0.0 github.com/onsi/gomega v1.18.1 github.com/pkg/errors v0.9.1 @@ -58,13 +60,13 @@ require ( github.com/rs/zerolog v1.26.1 github.com/sciencemesh/meshdirectory-web v1.0.4 github.com/sethvargo/go-password v0.2.0 - github.com/stretchr/objx v0.3.0 // indirect github.com/stretchr/testify v1.7.0 github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f github.com/thanhpk/randstr v1.0.4 github.com/tidwall/pretty v1.2.0 // indirect github.com/tus/tusd v1.8.0 github.com/wk8/go-ordered-map v0.2.0 + go-micro.dev/v4 v4.3.1-0.20211108085239-0c2041e43908 go.mongodb.org/mongo-driver v1.7.2 // indirect go.opencensus.io v0.23.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0 @@ -72,10 +74,10 @@ require ( go.opentelemetry.io/otel/exporters/jaeger v1.3.0 go.opentelemetry.io/otel/sdk v1.3.0 go.opentelemetry.io/otel/trace v1.3.0 - golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e + golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e + golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 golang.org/x/term v0.0.0-20210916214954-140adaaadfaf google.golang.org/genproto v0.0.0-20211021150943-2b146023228c google.golang.org/grpc v1.42.0 diff --git a/go.sum b/go.sum index a86e76abb8e..fea989cc027 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= bou.ke/monkey v1.0.2 h1:kWcnsrCNUatbxncxR/ThdYqbytgOIArtYWqcQLQzKLI= bou.ke/monkey v1.0.2/go.mod h1:OqickVX3tNx6t33n1xvtTtu85YN5s6cKwVug+oHMaIA= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= @@ -36,6 +37,7 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -46,16 +48,33 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.18.2/go.mod h1:AiIj7BWXyhO5gGVmYJ+S8tbkCx3yb0IMjua8Aw4naVM= +contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA= contrib.go.opencensus.io/exporter/prometheus v0.4.0 h1:0QfIkj9z/iVZgK31D9H9ohjjIDApI2GOPScCKwxedbs= contrib.go.opencensus.io/exporter/prometheus v0.4.0/go.mod h1:o7cosnyfuPVK0tB8q0QmaQNhGnptITnPQB+z1+qeFB0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= +github.com/Azure/azure-sdk-for-go v32.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.1.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest/adal v0.1.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/azure/auth v0.1.0/go.mod h1:Gf7/i2FUpyb/sGBLIFxTBzrNzBo7aPXXE3ZVeDRwdpM= +github.com/Azure/go-autorest/autorest/azure/cli v0.1.0/go.mod h1:Dk8CUAt/b/PzkfeRsWzVG9Yj3ps8mS8ECztu43rdU8U= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc= +github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= @@ -63,30 +82,51 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/ReneKroon/ttlcache/v2 v2.11.0 h1:OvlcYFYi941SBN3v9dsDcC2N8vRxyHcCmJb3Vl4QMoM= github.com/ReneKroon/ttlcache/v2 v2.11.0/go.mod h1:mBxvsNY+BT8qLLd6CuAJubbKo6r0jh3nb5et22bbfGY= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.0/go.mod h1:kX6YddBkXqqywAe8c9LyvgTCyFuZCTMF4cRPQhc3Fy8= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.976/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA= github.com/andrewmostello/go-tus v0.0.0-20200314041820-904a9904af9a h1:6tD4saJb8wmYF6Llz96ZJwUQ5r2GyTBFA2VEB5z8gVY= github.com/andrewmostello/go-tus v0.0.0-20200314041820-904a9904af9a/go.mod h1:XYuK1S5+kS6FGhlIUFuZFPvWiSrOIoLk6+ro33Xce3Y= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM= +github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= @@ -94,9 +134,12 @@ github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:o github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/asim/go-micro/plugins/events/nats/v4 v4.0.0-20220118152736-9e0be6c85d75 h1:G5Degn+tmIBpRCD7vPVpCoAol2gd/S7s00z5CWpzp5U= +github.com/asim/go-micro/plugins/events/nats/v4 v4.0.0-20220118152736-9e0be6c85d75/go.mod h1:BxrcQ4TPPMevB2udKEAHenQxCUh1xXVItoU2CbvVdcQ= github.com/aws/aws-sdk-go v1.20.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= github.com/aws/aws-sdk-go v1.35.24/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= +github.com/aws/aws-sdk-go v1.37.27/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.40.11/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go v1.41.13/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= @@ -109,15 +152,20 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw= github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40 h1:y4B3+GPxKlrigF1ha5FFErxK+sr6sWxQovRMzwMhejo= github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/c-bata/go-prompt v0.2.5 h1:3zg6PecEywxNn0xiqcXHD96fkbxghD+gdB2tbsYfl+Y= github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw= +github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/ceph/go-ceph v0.13.0 h1:69dgIPlNHD2OCz98T0benI4++vcnShGcpQK4RIALjw4= github.com/ceph/go-ceph v0.13.0/go.mod h1:mafFpf5Vg8Ai8Bd+FAMvKBHLmtdpTXdRP/TNq8XWegY= @@ -130,45 +178,103 @@ github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuP github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= +github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk= github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e h1:tqSPWQeueWTKnJVMJffz4pz0o1WuQxJ28+5x5JgaHD8= github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e/go.mod h1:XJEZ3/EQuI3BXTp/6DUzFr850vlxq11I6satRtz0YQ4= github.com/cs3org/go-cs3apis v0.0.0-20220126114148-64c025ccdd19 h1:1jqPH58jCxvbaJ9WLIJ7W2/m622bWS6ChptzljSG6IQ= github.com/cs3org/go-cs3apis v0.0.0-20220126114148-64c025ccdd19/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 h1:Z9lwXumT5ACSmJ7WGnFl+OMLLjpz5uR2fyz7dC255FI= github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8/go.mod h1:4abs/jPXcmJzYoYGF91JF9Uq9s/KL5n1jvFDix8KcqY= +github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deepmap/oapi-codegen v1.3.11/go.mod h1:suMvK7+rKlx3+tpa8ByptmvoXbAV70wERKTOGH3hLp0= github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/dnsimple/dnsimple-go v0.63.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg= +github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/ef-ds/deque v1.0.4/go.mod h1:gXDnTC3yqvBcHbq2lcExjtAcVrOnJCbMcZXmuj8Z4tg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch/v5 v5.5.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= +github.com/exoscale/egoscale v0.46.0/go.mod h1:mpEXBpROAa/2i5GC0r33rfxG+TxSEka11g1PIXt9+zc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsouza/go-dockerclient v1.7.3/go.mod h1:8xfZB8o9SptLNJ13VoV5pMiRbZGWkU/Omu5VOu/KC9Y= github.com/gdexlab/go-render v1.0.1 h1:rxqB3vo5s4n1kF0ySmoNeSPRYkEsyHgln4jFIQY7v0U= github.com/gdexlab/go-render v1.0.1/go.mod h1:wRi5nW2qfjiGj4mPukH4UV0IknS1cHD4VgFTmJX5JzM= +github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-acme/lego/v4 v4.4.0/go.mod h1:l3+tFUFZb590dWcqhWZegynUthtaHJbG2fevUpoOOE0= github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs= +github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= +github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -276,12 +382,14 @@ github.com/go-openapi/validate v0.20.1/go.mod h1:b60iJT+xNNLfaQJUqLI7946tYiFEOuE github.com/go-openapi/validate v0.20.2/go.mod h1:e7OJoKNgd0twXZwIn0A43tHbvIcr/rZIVCbJBpTUoY0= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= +github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= @@ -306,17 +414,27 @@ github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWe github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -353,6 +471,7 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/gomodule/redigo v1.8.8 h1:f6cXq6RRfiyrOJEV7p3JhLDlmawGBVBBP1MggY8Mo4E= github.com/gomodule/redigo v1.8.8/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -373,6 +492,8 @@ github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -405,28 +526,68 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/gophercloud/gophercloud v0.15.1-0.20210202035223-633d73521055/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4= +github.com/gophercloud/gophercloud v0.16.0/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4= +github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae/go.mod h1:wx8HMD8oQD0Ryhz6+6ykq75PJ79iPyEqYHfwZ4l7OsA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.1.0 h1:QsGcniKx5/LuX2eYoeL+Np3UKYPNaN7YKpTh29h8rbw= github.com/hashicorp/go-hclog v1.1.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v1.1.5 h1:9byZdVjKTe5mce63pRVNP1L7UAmdHOTEMGehn6KvJWs= +github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.2.4/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/raft v1.3.1/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q+7HVbyzqM= +github.com/hashicorp/raft v1.3.3 h1:Xr6DSHC5cIM8kzxu+IgoT/+MeNeUNeWin3ie6nlSrMg= +github.com/hashicorp/raft v1.3.3/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q+7HVbyzqM= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -434,11 +595,16 @@ github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI= +github.com/jarcoal/httpmock v1.0.6/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo= github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= @@ -448,8 +614,10 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -463,21 +631,27 @@ github.com/juliangruber/go-intersect v1.1.0 h1:sc+y5dCjMMx0pAdYk/N6KBm00tD/f3tq+ github.com/juliangruber/go-intersect v1.1.0/go.mod h1:WMau+1kAmnlQnKiikekNJbtGtfmILU/mMU6H7AgKbWQ= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.5 h1:9O69jUPDcsT9fEm74W92rZL9FQY7rCdaXVneq+yyzl4= github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= +github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -489,7 +663,21 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA= +github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w= +github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= +github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= +github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/linode/linodego v0.25.3/go.mod h1:GSBKPpjoQfxEfryoCRcgkuUOCuVtGHWhzI8OMdycNTE= +github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= +github.com/liquidweb/go-lwApi v0.0.5/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= +github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ= +github.com/liquidweb/liquidweb-go v1.6.3/go.mod h1:SuXXp+thr28LnjEw18AYtWwIbWMHSUiajPQs8T9c/Rc= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -498,10 +686,14 @@ github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7 github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -510,21 +702,29 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.14.10 h1:MLn+5bFRlWMGoSRmJour3CL1w/qL96mvipqpwQW/Sfk= github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI= github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/maxymania/go-system v0.0.0-20170110133659-647cc364bf0b h1:Q53idHrTuQDDHyXaxZ6pUl0I9uyD6Z6uKFK3ocX6LzI= github.com/maxymania/go-system v0.0.0-20170110133659-647cc364bf0b/go.mod h1:KirJrATYGbTyUwVR26xIkaipRqRcMRXBf8N5dacvGus= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= +github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/mileusna/useragent v1.0.2 h1:DgVKtiPnjxlb73z9bCwgdUvU2nQNQ97uhgfO8l9uz/w= github.com/mileusna/useragent v1.0.2/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc= +github.com/minio/highwayhash v1.0.1 h1:dZ6IIu8Z14VlC0VpfKofAhCy74wu/Qb5gcn52yWoz/0= +github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4= github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw= github.com/minio/minio-go/v7 v7.0.21 h1:xrc4BQr1Fa4s5RwY0xfMjPZFJ1bcYBCCHYlngBdWV+k= @@ -534,12 +734,18 @@ github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -550,6 +756,9 @@ github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGg github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/sys/mount v0.2.0/go.mod h1:aAivFE2LB3W4bACsUXChRHQ0qKWsetY4Y9V7sxOougM= +github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -557,10 +766,40 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8= +github.com/nats-io/jwt/v2 v2.1.0/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k= +github.com/nats-io/jwt/v2 v2.2.1-0.20220113022732-58e87895b296 h1:vU9tpM3apjYlLLeY23zRWJ9Zktr5jp+mloR942LEOpY= +github.com/nats-io/jwt/v2 v2.2.1-0.20220113022732-58e87895b296/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k= +github.com/nats-io/nats-server/v2 v2.6.2/go.mod h1:CNi6dJQ5H+vWqaoWKjCGtqBt7ai/xOTLiocUqhK6ews= +github.com/nats-io/nats-server/v2 v2.6.4/go.mod h1:LlMieumxNUnCloOTVFv7Wog0YnasScxARUMXVXv9/+M= +github.com/nats-io/nats-server/v2 v2.7.2 h1:+LEN8m0+jdCkiGc884WnDuxR+qj80/5arj+szKuRpRI= +github.com/nats-io/nats-server/v2 v2.7.2/go.mod h1:tckmrt0M6bVaDT3kmh9UrIq/CBOBBse+TpXQi5ldaa8= +github.com/nats-io/nats-streaming-server v0.23.0/go.mod h1:1asNNRpUKbgwoPqRLEWbJE65uqmWjG1YN/Xlo3WgkTY= +github.com/nats-io/nats-streaming-server v0.24.1 h1:autzhooN72ELtqP3alC2OPzmrbiA6jIZaQmKdLQsckk= +github.com/nats-io/nats-streaming-server v0.24.1/go.mod h1:N2Q05hKD+aW2Ur1VYP85yUR2zUWHbqJG88CxAFLRrd4= +github.com/nats-io/nats.go v1.11.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nats.go v1.13.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nats.go v1.13.1-0.20211018182449-f2416a8b1483/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nats.go v1.13.1-0.20220121202836-972a071d373d h1:GRSmEJutHkdoxKsRypP575IIdoXe7Bm6yHQF6GcDBnA= +github.com/nats-io/nats.go v1.13.1-0.20220121202836-972a071d373d/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8= +github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nats-io/stan.go v0.10.0/go.mod h1:0jEuBXKauB1HHJswHM/lx05K48TJ1Yxj6VIfM4k+aB4= +github.com/nats-io/stan.go v0.10.2 h1:gQLd05LhzmhFkHm3/qP/klYHfM/hys45GyHa1Uly/kI= +github.com/nats-io/stan.go v0.10.2/go.mod h1:vo2ax8K2IxaR3JtEMLZRFKIdoK/3o1/PKueapB7ezX0= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nrdcg/auroradns v1.0.1/go.mod h1:y4pc0i9QXYlFCWrhWrUSIETnZgrf4KuwjDIWmmXo3JI= +github.com/nrdcg/desec v0.5.0/go.mod h1:2ejvMazkav1VdDbv2HeQO7w+Ta1CGHqzQr27ZBYTuEQ= +github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ= +github.com/nrdcg/goinwx v0.8.1/go.mod h1:tILVc10gieBp/5PMvbcYeXM6pVQ+c9jxDZnpaR1UW7c= +github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw= +github.com/nrdcg/porkbun v0.1.1/go.mod h1:JWl/WKnguWos4mjfp4YizvvToigk9qpQwrodOk+CPoA= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= @@ -568,6 +807,7 @@ github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= @@ -581,16 +821,35 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= +github.com/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk= github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= github.com/pkg/xattr v0.4.4 h1:FSoblPdYobYoKCItkqASqcrKCxRn9Bgurz0sCBwzO5g= @@ -600,19 +859,30 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc= github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= +github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/prometheus/alertmanager v0.23.0 h1:KIb9IChC3kg+1CC388qfr7bsT+tARpQqdsCMoatdObA= github.com/prometheus/alertmanager v0.23.0/go.mod h1:0MLTrjQI8EuVmvykEhcfr/7X0xmaDAZrqMgxIq3OXHk= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= @@ -621,13 +891,23 @@ github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= github.com/prometheus/exporter-toolkit v0.6.1/go.mod h1:ZUBIj498ePooX9t/2xtDjeQYwvRpiPP2lh5u4iblj2g= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/statsd_exporter v0.21.0 h1:hA05Q5RFeIjgwKIYEdFd59xu5Wwaznf33yKI+pyX6T8= github.com/prometheus/statsd_exporter v0.21.0/go.mod h1:rbT83sZq2V+p73lHhPZfMc3MLCHmSHelCh9hSGYNLTQ= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKcyumwBO6qip7RNQ5r77yrssm9bfCowcLEBcU5IA= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -639,16 +919,22 @@ github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sacloud/libsacloud v1.36.2/go.mod h1:P7YAOVmnIn3DKHqCZcUKYUXmSwGBm3yS7IBEjKVSrjg= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8= github.com/sciencemesh/meshdirectory-web v1.0.4 h1:1YSctF6PAXhoHUYCaeRTj7rHaF7b3rYrZf2R0VXBIbo= github.com/sciencemesh/meshdirectory-web v1.0.4/go.mod h1:fJSThTS3xf+sTdL0iXQoaQJssLI7tn7DetHMHUl4SRk= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sethgrid/pester v0.0.0-20190127155807-68a33a018ad0/go.mod h1:Ad7IjTpvzZO8Fl0vh9AzQ+j/jYZfyp2diGwI8m5q+ns= github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI= github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 h1:pXY9qYc/MP5zdvqWEUH6SjNiu7VhSjuVFTFiTcphaLU= github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -656,15 +942,34 @@ github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= +github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= +github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -679,27 +984,47 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f h1:L2NE7BXnSlSLoNYZ0lCwZDjdnYjCNYC71k9ClZUTFTs= github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/thanhpk/randstr v1.0.4 h1:IN78qu/bR+My+gHCvMEXhR/i5oriVHcTB/BJJIRTsNo= github.com/thanhpk/randstr v1.0.4/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/transip/gotransip/v6 v6.2.0/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g= github.com/tus/tusd v1.1.0/go.mod h1:3DWPOdeCnjBwKtv98y5dSws3itPqfce5TVa0s59LRiA= github.com/tus/tusd v1.8.0 h1:QODQ5uMhL2tFX3Ouk7rUHHqPqeDBvi2+gYIoyUO0n8Q= github.com/tus/tusd v1.8.0/go.mod h1:stZzKpol4qz7lX2HXy/1H526dn5mRnkIICTW2lrh9NM= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= +github.com/uber/jaeger-client-go v2.29.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/vimeo/go-util v1.2.0/go.mod h1:s13SMDTSO7AjH1nbgp707mfN5JFIWUFDU5MDDuRRtKs= github.com/vimeo/go-util v1.4.1/go.mod h1:r+yspV//C48HeMXV8nEvtUeNiIiGfVv3bbEHzOgudwE= +github.com/vinyldns/go-vinyldns v0.0.0-20200917153823-148a5f6b8f14/go.mod h1:RWc47jtnVuQv6+lY3c768WtXCas/Xi+U5UFc5xULmYg= +github.com/vultr/govultr/v2 v2.0.0/go.mod h1:2PsEeg+gs3p/Fo5Pw8F9mv+DUBEOlrNZ8GmCTGmhOhs= github.com/wk8/go-ordered-map v0.2.0 h1:KlvGyHstD1kkGZkPtHCyCfRYS0cz84uk6rrW/Dnhdtk= github.com/wk8/go-ordered-map v0.2.0/go.mod h1:9ZIbRunKbuvfPKyBP1SIKLcXNlv74YCOZ3t3VTS6gRk= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -708,6 +1033,12 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go-micro.dev/v4 v4.3.1-0.20211108085239-0c2041e43908 h1:4ori3xawGl2unFIOQPEgUuHdlrvihc+dsIot7XUzc2k= +go-micro.dev/v4 v4.3.1-0.20211108085239-0c2041e43908/go.mod h1:tw47Xfg2YywfPUnglZgXQsSf7p0ST6mQL3v0JooGmSY= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= +go.m3o.com v0.1.0/go.mod h1:p8FdLqZH3R9a0y04qiMNT+clw69d3SxyQPFzCNbDRtk= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= @@ -718,6 +1049,8 @@ go.mongodb.org/mongo-driver v1.4.6/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4S go.mongodb.org/mongo-driver v1.5.1/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw= go.mongodb.org/mongo-driver v1.7.2 h1:pFttQyIiJUHEn50YfZgC9ECjITMT44oiN36uArf/OFg= go.mongodb.org/mongo-driver v1.7.2/go.mod h1:Q4oFMbo1+MSNqICAdYMlC/zSTrwCogR4R8NzkI+yfU8= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -736,32 +1069,52 @@ go.opentelemetry.io/otel/sdk v1.3.0 h1:3278edCoH89MEJ0Ky8WQXVmDQv3FX4ZJ3Pp+9fJre go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= go.opentelemetry.io/otel/trace v1.3.0 h1:doy8Hzb1RJ+I3yFhtDmwNc7tIyw1tNMOIsyPzp1NOGY= go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e h1:1SzTfNOXwIS2oWiMF+6qu0OUDKb0dauo6MoDUQyu+yU= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce h1:Roh6XWxHFKrPgC/EQhVubSAGQ6Ozk6IdxHSzt1mR0EI= +golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= @@ -771,6 +1124,7 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -800,8 +1154,12 @@ golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -815,6 +1173,7 @@ golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -839,15 +1198,19 @@ golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -880,10 +1243,15 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -900,21 +1268,26 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -931,24 +1304,32 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201110211018-35f3e6cf4a65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210216224549-f992740a1bac/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -961,9 +1342,13 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201113234701-d7a72108b828/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210916214954-140adaaadfaf h1:Ihq/mm/suC88gF8WFcVwk+OV6Tq+wyA1O0E5UEvDglI= @@ -980,10 +1365,18 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M= +golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -992,6 +1385,7 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190424220101-1e8e1cfdf96b/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -1000,17 +1394,20 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -1053,6 +1450,11 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -1085,6 +1487,7 @@ google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqiv google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E= google.golang.org/api v0.60.0/go.mod h1:d7rl65NZAkEQ90JFzqBjcRq1TVeG5ZoGV3sSpEnnVb4= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= @@ -1093,6 +1496,7 @@ google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -1184,14 +1588,25 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/h2non/gock.v1 v1.0.14/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= +gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= -gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= +gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ns1/ns1-go.v2 v2.4.4/go.mod h1:GMnKY+ZuoJ+lVLL+78uSTjwTz2jMazq6AfGKQOYhsPk= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1205,6 +1620,8 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1213,5 +1630,6 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/grpc/interceptors/eventsmiddleware/conversion.go b/internal/grpc/interceptors/eventsmiddleware/conversion.go new file mode 100644 index 00000000000..aa1ef2453da --- /dev/null +++ b/internal/grpc/interceptors/eventsmiddleware/conversion.go @@ -0,0 +1,37 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package eventsmiddleware + +import ( + collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + "github.com/cs3org/reva/pkg/events" +) + +// ShareCreated converts response to event +func ShareCreated(r *collaboration.CreateShareResponse) events.ShareCreated { + e := events.ShareCreated{ + Sharer: r.Share.Creator, + GranteeUserID: r.Share.GetGrantee().GetUserId(), + GranteeGroupID: r.Share.GetGrantee().GetGroupId(), + ItemID: r.Share.ResourceId, + CTime: r.Share.Ctime, + } + + return e +} diff --git a/internal/grpc/interceptors/eventsmiddleware/events.go b/internal/grpc/interceptors/eventsmiddleware/events.go new file mode 100644 index 00000000000..d7603e74c52 --- /dev/null +++ b/internal/grpc/interceptors/eventsmiddleware/events.go @@ -0,0 +1,95 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package eventsmiddleware + +import ( + "context" + "fmt" + + "go-micro.dev/v4/util/log" + "google.golang.org/grpc" + + "github.com/asim/go-micro/plugins/events/nats/v4" + collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + "github.com/cs3org/reva/pkg/events" + "github.com/cs3org/reva/pkg/events/server" + "github.com/cs3org/reva/pkg/rgrpc" +) + +const ( + defaultPriority = 200 +) + +func init() { + rgrpc.RegisterUnaryInterceptor("eventsmiddleware", NewUnary) +} + +// NewUnary returns a new unary interceptor that emits events when needed +// no lint because of the switch statement that should be extendable +//nolint:gocritic +func NewUnary(m map[string]interface{}) (grpc.UnaryServerInterceptor, int, error) { + publisher, err := publisherFromConfig(m) + if err != nil { + return nil, 0, err + } + + interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + res, err := handler(ctx, req) + if err != nil { + return res, err + } + + var ev interface{} + switch v := res.(type) { + case *collaboration.CreateShareResponse: + ev = ShareCreated(v) + } + + if ev != nil { + if err := events.Publish(publisher, ev); err != nil { + log.Error(err) + } + } + + return res, nil + } + return interceptor, defaultPriority, nil +} + +// NewStream returns a new server stream interceptor +// that creates the application context. +func NewStream() grpc.StreamServerInterceptor { + interceptor := func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + // TODO: Use ss.RecvMsg() and ss.SendMsg() to send events from a stream + return handler(srv, ss) + } + return interceptor +} + +func publisherFromConfig(m map[string]interface{}) (events.Publisher, error) { + typ := m["type"].(string) + switch typ { + default: + return nil, fmt.Errorf("stream type '%s' not supported", typ) + case "nats": + address := m["address"].(string) + cid := m["clusterID"].(string) + return server.NewNatsStream(nats.Address(address), nats.ClusterID(cid)) + } +} diff --git a/internal/grpc/interceptors/loader/loader.go b/internal/grpc/interceptors/loader/loader.go index c5f252e3977..f0d525f045d 100644 --- a/internal/grpc/interceptors/loader/loader.go +++ b/internal/grpc/interceptors/loader/loader.go @@ -20,6 +20,7 @@ package loader import ( // Load core GRPC services + _ "github.com/cs3org/reva/internal/grpc/interceptors/eventsmiddleware" _ "github.com/cs3org/reva/internal/grpc/interceptors/readonly" // Add your own service here ) diff --git a/pkg/events/events.go b/pkg/events/events.go new file mode 100644 index 00000000000..40b461144e0 --- /dev/null +++ b/pkg/events/events.go @@ -0,0 +1,107 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package events + +import ( + "log" + "reflect" + + "go-micro.dev/v4/events" +) + +var ( + // MainQueueName is the name of the main queue + // All events will go through here as they are forwarded to the consumer via the + // group name + // TODO: "fan-out" so not all events go through the same queue? requires investigation + MainQueueName = "main-queue" + + // MetadatakeyEventType is the key used for the eventtype in the metadata map of the event + MetadatakeyEventType = "eventtype" +) + +type ( + // Unmarshaller is the interface events need to fulfill + Unmarshaller interface { + Unmarshal([]byte) (interface{}, error) + } + + // Publisher is the interface publishers need to fulfill + Publisher interface { + Publish(string, interface{}, ...events.PublishOption) error + } + + // Consumer is the interface consumer need to fulfill + Consumer interface { + Consume(string, ...events.ConsumeOption) (<-chan events.Event, error) + } + + // Stream is the interface common to Publisher and Consumer + Stream interface { + Publish(string, interface{}, ...events.PublishOption) error + Consume(string, ...events.ConsumeOption) (<-chan events.Event, error) + } +) + +// Consume returns a channel that will get all events that match the given evs +// group defines the service type: One group will get exactly one copy of a event that is emitted +// NOTE: uses reflect on initialization +func Consume(s Consumer, group string, evs ...Unmarshaller) (<-chan interface{}, error) { + c, err := s.Consume(MainQueueName, events.WithGroup(group)) + if err != nil { + return nil, err + } + + registeredEvents := map[string]Unmarshaller{} + for _, e := range evs { + typ := reflect.TypeOf(e) + registeredEvents[typ.String()] = e + } + + outchan := make(chan interface{}) + go func() { + for { + e := <-c + et := e.Metadata[MetadatakeyEventType] + ev, ok := registeredEvents[et] + if !ok { + log.Printf("not registered: %s", et) + continue + } + + event, err := ev.Unmarshal(e.Payload) + if err != nil { + log.Printf("can't unmarshal event %v", err) + continue + } + + outchan <- event + } + }() + return outchan, nil +} + +// Publish publishes the ev to the MainQueue from where it is distributed to all subscribers +// NOTE: needs to use reflect on runtime +func Publish(s Publisher, ev interface{}) error { + evName := reflect.TypeOf(ev).String() + return s.Publish(MainQueueName, ev, events.WithMetadata(map[string]string{ + MetadatakeyEventType: evName, + })) +} diff --git a/pkg/events/example/consumer/consumer.go b/pkg/events/example/consumer/consumer.go new file mode 100644 index 00000000000..caeaac2e161 --- /dev/null +++ b/pkg/events/example/consumer/consumer.go @@ -0,0 +1,60 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// Package consumer contains an example implementation of an event consumer +package consumer + +import ( + "fmt" + "log" + + "github.com/cs3org/reva/pkg/events" +) + +// Example consumes events from the queue +func Example(c events.Consumer) { + // Step 1 - which group does the consumer belong to? + // each group will get each event that is emitted, but only one member of the group will get it. + group := "test-consumer" + + // Step 2 - which events does the consumer listen too? + evs := []events.Unmarshaller{ + // for example created shares + events.ShareCreated{}, + } + + // Step 3 - create event channel + evChan, err := events.Consume(c, group, evs...) + if err != nil { + log.Fatal(err) + } + + // Step 4 - listen to events + for { + event := <-evChan + + // best to use type switch to differentiate events + switch v := event.(type) { + case events.ShareCreated: + fmt.Printf("%s) Share created: %+v\n", group, v) + default: + fmt.Printf("%s) Unregistered event: %+v\n", group, v) + } + } + +} diff --git a/pkg/events/example/example.go b/pkg/events/example/example.go new file mode 100644 index 00000000000..9240fc74db7 --- /dev/null +++ b/pkg/events/example/example.go @@ -0,0 +1,70 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package main + +import ( + "log" + "time" + + "github.com/asim/go-micro/plugins/events/nats/v4" + "github.com/cs3org/reva/pkg/events" + "github.com/cs3org/reva/pkg/events/example/consumer" + "github.com/cs3org/reva/pkg/events/example/publisher" + "github.com/cs3org/reva/pkg/events/server" +) + +// Simple example of an event workflow +func main() { + // start a server + Server() + + // obtain a client + c := Client() + + // register a consumer + go consumer.Example(c) + + // NOTE: consumer must be registered to get events + time.Sleep(time.Millisecond) + + // Publish an event + publisher.Example(c) + + // wait for consumer go-routine to print + time.Sleep(500 * time.Millisecond) + +} + +// Server generates a nats server +func Server() { + err := server.RunNatsServer() + if err != nil { + log.Fatal(err) + } +} + +// Client builds a nats client +func Client() events.Stream { + c, err := server.NewNatsStream(nats.Address("127.0.0.1:4222"), nats.ClusterID("test-cluster")) + if err != nil { + log.Fatal(err) + } + return c + +} diff --git a/pkg/events/example/publisher/publisher.go b/pkg/events/example/publisher/publisher.go new file mode 100644 index 00000000000..ac7ccd686ac --- /dev/null +++ b/pkg/events/example/publisher/publisher.go @@ -0,0 +1,44 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// Package publisher contains an example implementation for a publisher +package publisher + +import ( + "log" + + user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + "github.com/cs3org/reva/pkg/events" +) + +// Example publishes events to the queue +func Example(p events.Publisher) { + // nothing to do - just publish! + err := events.Publish(p, events.ShareCreated{ + Sharer: &user.UserId{ + OpaqueId: "123", + }, + GranteeUserID: &user.UserId{ + OpaqueId: "456", + }, + }) + if err != nil { + log.Fatal(err) + } + +} diff --git a/pkg/events/server/nats.go b/pkg/events/server/nats.go new file mode 100644 index 00000000000..017dac0cc5d --- /dev/null +++ b/pkg/events/server/nats.go @@ -0,0 +1,43 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package server + +import ( + "github.com/asim/go-micro/plugins/events/nats/v4" + "go-micro.dev/v4/events" + + stanServer "github.com/nats-io/nats-streaming-server/server" +) + +// RunNatsServer runs the nats streaming server +func RunNatsServer(opts ...Option) error { + natsOpts := stanServer.DefaultNatsServerOptions + stanOpts := stanServer.GetDefaultOptions() + + for _, o := range opts { + o(&natsOpts, stanOpts) + } + _, err := stanServer.RunServerWithOpts(stanOpts, &natsOpts) + return err +} + +// NewNatsStream returns a streaming client used by `Consume` and `Publish` methods +func NewNatsStream(opts ...nats.Option) (events.Stream, error) { + return nats.NewStream(opts...) +} diff --git a/pkg/events/server/options.go b/pkg/events/server/options.go new file mode 100644 index 00000000000..2cb40fa3ae8 --- /dev/null +++ b/pkg/events/server/options.go @@ -0,0 +1,55 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package server + +import ( + natsServer "github.com/nats-io/nats-server/v2/server" + stanServer "github.com/nats-io/nats-streaming-server/server" +) + +// Option configures the nats server +type Option func(*natsServer.Options, *stanServer.Options) + +// Host sets the host URL for the nats server +func Host(url string) Option { + return func(no *natsServer.Options, _ *stanServer.Options) { + no.Host = url + } +} + +// Port sets the host URL for the nats server +func Port(port int) Option { + return func(no *natsServer.Options, _ *stanServer.Options) { + no.Port = port + } +} + +// NatsOpts allows setting Options from nats package directly +func NatsOpts(opt func(*natsServer.Options)) Option { + return func(no *natsServer.Options, _ *stanServer.Options) { + opt(no) + } +} + +// StanOpts allows setting Options from stan package directly +func StanOpts(opt func(*stanServer.Options)) Option { + return func(_ *natsServer.Options, so *stanServer.Options) { + opt(so) + } +} diff --git a/pkg/events/types.go b/pkg/events/types.go new file mode 100644 index 00000000000..3ca9dd7f802 --- /dev/null +++ b/pkg/events/types.go @@ -0,0 +1,46 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package events + +import ( + "encoding/json" + + group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" + user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" +) + +// ShareCreated is emitted when a share is created +type ShareCreated struct { // TODO: Rename to ShareCreatedEvent? + Sharer *user.UserId + // split the protobuf Grantee oneof so we can use stdlib encoding/json + GranteeUserID *user.UserId + GranteeGroupID *group.GroupId + Sharee *provider.Grantee + ItemID *provider.ResourceId + CTime *types.Timestamp +} + +// Unmarshal to fulfill umarshaller interface +func (ShareCreated) Unmarshal(v []byte) (interface{}, error) { + e := ShareCreated{} + err := json.Unmarshal(v, &e) + return e, err +} From 31f0609cd78c588b879ba2d814bbd68a6e54e14d Mon Sep 17 00:00:00 2001 From: PKiran <39373750+kiranparajuli589@users.noreply.github.com> Date: Fri, 11 Feb 2022 16:22:36 +0545 Subject: [PATCH 46/49] [tests-only][full-ci] Bump core commit 2022 02 11 (#2529) * Updated core commit id to the latest upto date 2022/02/11 * Update expected to failures to include more incoming tests --- .drone.env | 2 +- .../expected-failures-on-OCIS-storage.md | 337 +++++++++++------- .../expected-failures-on-S3NG-storage.md | 333 ++++++++++------- 3 files changed, 423 insertions(+), 249 deletions(-) diff --git a/.drone.env b/.drone.env index 5bca50c0099..7a5f328e642 100644 --- a/.drone.env +++ b/.drone.env @@ -1,3 +1,3 @@ # The test runner source for API tests -CORE_COMMITID=a0fb3af23939f5573c7b0a54d68204b6c705ed81 +CORE_COMMITID=3e0e77186b4f0236d13d5ad1bc1454f7a0873d77 CORE_BRANCH=master diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index 7d608709a02..072cbf55d5e 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -172,65 +172,78 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks/requestsWithToken.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L132) - [apiWebdavLocks/requestsWithToken.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L108) - [apiWebdavLocks/requestsWithToken.feature:109](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L109) -- [apiWebdavLocks/requestsWithToken.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#156) -- [apiWebdavLocks/requestsWithToken.feature:157](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#157) +- [apiWebdavLocks/requestsWithToken.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L156) +- [apiWebdavLocks/requestsWithToken.feature:157](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L157) - [apiWebdavLocks/requestsWithToken.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L162) - [apiWebdavLocks2/resharedSharesToShares.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L33) - [apiWebdavLocks2/resharedSharesToShares.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L34) - [apiWebdavLocks2/resharedSharesToShares.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L35) - [apiWebdavLocks2/resharedSharesToShares.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L36) -- [apiWebdavLocks2/resharedSharesToShares.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L58) -- [apiWebdavLocks2/resharedSharesToShares.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L59) -- [apiWebdavLocks2/resharedSharesToShares.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L60) -- [apiWebdavLocks2/resharedSharesToShares.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L61) -- [apiWebdavLocks2/resharedSharesToShares.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L78) -- [apiWebdavLocks2/resharedSharesToShares.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L79) -- [apiWebdavLocks2/resharedSharesToShares.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L80) -- [apiWebdavLocks2/resharedSharesToShares.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L81) -- [apiWebdavLocks2/resharedSharesToShares.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L101) -- [apiWebdavLocks2/resharedSharesToShares.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L102) -- [apiWebdavLocks2/resharedSharesToShares.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L103) -- [apiWebdavLocks2/resharedSharesToShares.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L104) -- [apiWebdavLocks2/resharedSharesToShares.feature:123](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L123) -- [apiWebdavLocks2/resharedSharesToShares.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L124) -- [apiWebdavLocks2/resharedSharesToShares.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L125) -- [apiWebdavLocks2/resharedSharesToShares.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L126) +- [apiWebdavLocks2/resharedSharesToShares.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L65) +- [apiWebdavLocks2/resharedSharesToShares.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L66) +- [apiWebdavLocks2/resharedSharesToShares.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L67) +- [apiWebdavLocks2/resharedSharesToShares.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L68) +- [apiWebdavLocks2/resharedSharesToShares.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L91) +- [apiWebdavLocks2/resharedSharesToShares.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L92) +- [apiWebdavLocks2/resharedSharesToShares.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L93) +- [apiWebdavLocks2/resharedSharesToShares.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L94) +- [apiWebdavLocks2/resharedSharesToShares.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L115) +- [apiWebdavLocks2/resharedSharesToShares.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L116) +- [apiWebdavLocks2/resharedSharesToShares.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L117) +- [apiWebdavLocks2/resharedSharesToShares.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L118) +- [apiWebdavLocks2/resharedSharesToShares.feature:144](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L144) +- [apiWebdavLocks2/resharedSharesToShares.feature:145](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L145) +- [apiWebdavLocks2/resharedSharesToShares.feature:146](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L146) +- [apiWebdavLocks2/resharedSharesToShares.feature:147](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L147) - [apiWebdavLocks2/setTimeout.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L32) - [apiWebdavLocks2/setTimeout.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L33) - [apiWebdavLocks2/setTimeout.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L34) - [apiWebdavLocks2/setTimeout.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L35) -- [apiWebdavLocks2/setTimeout.feature:56](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L56) -- [apiWebdavLocks2/setTimeout.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L57) -- [apiWebdavLocks2/setTimeout.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L58) -- [apiWebdavLocks2/setTimeout.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L59) -- [apiWebdavLocks2/setTimeout.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L60) -- [apiWebdavLocks2/setTimeout.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L61) -- [apiWebdavLocks2/setTimeout.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L62) +- [apiWebdavLocks2/setTimeout.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L40) +- [apiWebdavLocks2/setTimeout.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L41) - [apiWebdavLocks2/setTimeout.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L63) - [apiWebdavLocks2/setTimeout.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L64) - [apiWebdavLocks2/setTimeout.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L65) -- [apiWebdavLocks2/setTimeout.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L89) -- [apiWebdavLocks2/setTimeout.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L90) -- [apiWebdavLocks2/setTimeout.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L91) -- [apiWebdavLocks2/setTimeout.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L92) -- [apiWebdavLocks2/setTimeout.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L93) -- [apiWebdavLocks2/setTimeout.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L94) -- [apiWebdavLocks2/setTimeout.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L95) -- [apiWebdavLocks2/setTimeout.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L96) -- [apiWebdavLocks2/setTimeout.feature:97](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L97) -- [apiWebdavLocks2/setTimeout.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L98) -- [apiWebdavLocks2/setTimeout.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L99) -- [apiWebdavLocks2/setTimeout.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L100) +- [apiWebdavLocks2/setTimeout.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L66) +- [apiWebdavLocks2/setTimeout.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L67) +- [apiWebdavLocks2/setTimeout.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L68) +- [apiWebdavLocks2/setTimeout.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L69) +- [apiWebdavLocks2/setTimeout.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L70) +- [apiWebdavLocks2/setTimeout.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L71) +- [apiWebdavLocks2/setTimeout.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L72) +- [apiWebdavLocks2/setTimeout.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L77) +- [apiWebdavLocks2/setTimeout.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L78) +- [apiWebdavLocks2/setTimeout.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L79) +- [apiWebdavLocks2/setTimeout.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L80) +- [apiWebdavLocks2/setTimeout.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L81) +- [apiWebdavLocks2/setTimeout.feature:105](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L105) +- [apiWebdavLocks2/setTimeout.feature:106](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L106) +- [apiWebdavLocks2/setTimeout.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L107) +- [apiWebdavLocks2/setTimeout.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L108) +- [apiWebdavLocks2/setTimeout.feature:109](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L109) +- [apiWebdavLocks2/setTimeout.feature:110](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L110) +- [apiWebdavLocks2/setTimeout.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L111) +- [apiWebdavLocks2/setTimeout.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L112) +- [apiWebdavLocks2/setTimeout.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L113) +- [apiWebdavLocks2/setTimeout.feature:114](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L114) +- [apiWebdavLocks2/setTimeout.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L115) +- [apiWebdavLocks2/setTimeout.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L116) +- [apiWebdavLocks2/setTimeout.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L121) +- [apiWebdavLocks2/setTimeout.feature:122](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L122) - [apiWebdavLocks2/setTimeout.feature:123](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L123) - [apiWebdavLocks2/setTimeout.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L124) - [apiWebdavLocks2/setTimeout.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L125) - [apiWebdavLocks2/setTimeout.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L126) -- [apiWebdavLocks2/setTimeout.feature:127](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L127) -- [apiWebdavLocks2/setTimeout.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L128) -- [apiWebdavLocks2/setTimeout.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L129) -- [apiWebdavLocks2/setTimeout.feature:130](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L130) -- [apiWebdavLocks2/setTimeout.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L131) -- [apiWebdavLocks2/setTimeout.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L132) +- [apiWebdavLocks2/setTimeout.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L149) +- [apiWebdavLocks2/setTimeout.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L150) +- [apiWebdavLocks2/setTimeout.feature:151](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L151) +- [apiWebdavLocks2/setTimeout.feature:152](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L152) +- [apiWebdavLocks2/setTimeout.feature:153](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L153) +- [apiWebdavLocks2/setTimeout.feature:154](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L154) +- [apiWebdavLocks2/setTimeout.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L155) +- [apiWebdavLocks2/setTimeout.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L156) +- [apiWebdavLocks2/setTimeout.feature:157](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L157) +- [apiWebdavLocks2/setTimeout.feature:158](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L158) - [apiWebdavLocks2/setTimeoutSharesToShares.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L40) - [apiWebdavLocks2/setTimeoutSharesToShares.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L41) - [apiWebdavLocks2/setTimeoutSharesToShares.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L42) @@ -241,96 +254,137 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks2/setTimeoutSharesToShares.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L47) - [apiWebdavLocks2/setTimeoutSharesToShares.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L48) - [apiWebdavLocks2/setTimeoutSharesToShares.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L49) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L73) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L74) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L75) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L76) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L77) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L78) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L79) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L80) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L81) - [apiWebdavLocks2/setTimeoutSharesToShares.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L82) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L83) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L84) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L85) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L86) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L87) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L88) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L89) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L90) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L91) - [apiWebdavLocks3/independentLocks.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L24) - [apiWebdavLocks3/independentLocks.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L25) - [apiWebdavLocks3/independentLocks.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L26) - [apiWebdavLocks3/independentLocks.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L27) -- [apiWebdavLocks3/independentLocks.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L43) -- [apiWebdavLocks3/independentLocks.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L44) -- [apiWebdavLocks3/independentLocks.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L45) -- [apiWebdavLocks3/independentLocks.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L46) -- [apiWebdavLocks3/independentLocks.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L62) -- [apiWebdavLocks3/independentLocks.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L63) -- [apiWebdavLocks3/independentLocks.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L64) -- [apiWebdavLocks3/independentLocks.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L65) +- [apiWebdavLocks3/independentLocks.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L32) +- [apiWebdavLocks3/independentLocks.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L33) +- [apiWebdavLocks3/independentLocks.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L49) +- [apiWebdavLocks3/independentLocks.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L50) +- [apiWebdavLocks3/independentLocks.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L51) +- [apiWebdavLocks3/independentLocks.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L52) +- [apiWebdavLocks3/independentLocks.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L57) +- [apiWebdavLocks3/independentLocks.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L58) +- [apiWebdavLocks3/independentLocks.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L74) +- [apiWebdavLocks3/independentLocks.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L75) +- [apiWebdavLocks3/independentLocks.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L76) +- [apiWebdavLocks3/independentLocks.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L77) +- [apiWebdavLocks3/independentLocks.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L82) - [apiWebdavLocks3/independentLocks.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L83) -- [apiWebdavLocks3/independentLocks.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L84) -- [apiWebdavLocks3/independentLocks.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L85) -- [apiWebdavLocks3/independentLocks.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L86) -- [apiWebdavLocks3/independentLocks.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L87) -- [apiWebdavLocks3/independentLocks.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L88) -- [apiWebdavLocks3/independentLocks.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L89) -- [apiWebdavLocks3/independentLocks.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L90) +- [apiWebdavLocks3/independentLocks.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L101) +- [apiWebdavLocks3/independentLocks.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L102) +- [apiWebdavLocks3/independentLocks.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L103) +- [apiWebdavLocks3/independentLocks.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L104) +- [apiWebdavLocks3/independentLocks.feature:105](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L105) +- [apiWebdavLocks3/independentLocks.feature:106](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L106) +- [apiWebdavLocks3/independentLocks.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L107) +- [apiWebdavLocks3/independentLocks.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L108) +- [apiWebdavLocks3/independentLocks.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L113) +- [apiWebdavLocks3/independentLocks.feature:114](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L114) +- [apiWebdavLocks3/independentLocks.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L115) +- [apiWebdavLocks3/independentLocks.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L116) - [apiWebdavLocks3/independentLocksShareToShares.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L27) - [apiWebdavLocks3/independentLocksShareToShares.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L28) - [apiWebdavLocks3/independentLocksShareToShares.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L29) - [apiWebdavLocks3/independentLocksShareToShares.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L30) -- [apiWebdavLocks3/independentLocksShareToShares.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L49) -- [apiWebdavLocks3/independentLocksShareToShares.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L50) -- [apiWebdavLocks3/independentLocksShareToShares.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L51) -- [apiWebdavLocks3/independentLocksShareToShares.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L52) -- [apiWebdavLocks3/independentLocksShareToShares.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L72) -- [apiWebdavLocks3/independentLocksShareToShares.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L73) -- [apiWebdavLocks3/independentLocksShareToShares.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L74) -- [apiWebdavLocks3/independentLocksShareToShares.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L75) -- [apiWebdavLocks3/independentLocksShareToShares.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L94) -- [apiWebdavLocks3/independentLocksShareToShares.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L95) -- [apiWebdavLocks3/independentLocksShareToShares.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L96) -- [apiWebdavLocks3/independentLocksShareToShares.feature:97](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L97) -- [apiWebdavLocksUnlock/unlock.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L37) -- [apiWebdavLocksUnlock/unlock.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L38) +- [apiWebdavLocks3/independentLocksShareToShares.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L35) +- [apiWebdavLocks3/independentLocksShareToShares.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L36) +- [apiWebdavLocks3/independentLocksShareToShares.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L55) +- [apiWebdavLocks3/independentLocksShareToShares.feature:56](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L56) +- [apiWebdavLocks3/independentLocksShareToShares.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L57) +- [apiWebdavLocks3/independentLocksShareToShares.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L58) +- [apiWebdavLocks3/independentLocksShareToShares.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L63) +- [apiWebdavLocks3/independentLocksShareToShares.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L64) +- [apiWebdavLocks3/independentLocksShareToShares.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L84) +- [apiWebdavLocks3/independentLocksShareToShares.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L85) +- [apiWebdavLocks3/independentLocksShareToShares.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L86) +- [apiWebdavLocks3/independentLocksShareToShares.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L87) +- [apiWebdavLocks3/independentLocksShareToShares.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L92) +- [apiWebdavLocks3/independentLocksShareToShares.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L93) +- [apiWebdavLocks3/independentLocksShareToShares.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L112) +- [apiWebdavLocks3/independentLocksShareToShares.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L113) +- [apiWebdavLocks3/independentLocksShareToShares.feature:114](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L114) +- [apiWebdavLocks3/independentLocksShareToShares.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L115) +- [apiWebdavLocks3/independentLocksShareToShares.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L120) +- [apiWebdavLocks3/independentLocksShareToShares.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L121) +- [apiWebdavLocksUnlock/unlock.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L30) +- [apiWebdavLocksUnlock/unlock.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L31) +- [apiWebdavLocksUnlock/unlock.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L46) +- [apiWebdavLocksUnlock/unlock.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L47) - [apiWebdavLocksUnlock/unlock.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L52) -- [apiWebdavLocksUnlock/unlock.feature:53](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L53) -- [apiWebdavLocksUnlock/unlock.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L54) -- [apiWebdavLocksUnlock/unlock.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L55) +- [apiWebdavLocksUnlock/unlock.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L68) - [apiWebdavLocksUnlock/unlock.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L69) - [apiWebdavLocksUnlock/unlock.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L70) -- [apiWebdavLocksUnlock/unlock.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L94) -- [apiWebdavLocksUnlock/unlock.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L95) -- [apiWebdavLocksUnlock/unlock.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L96) -- [apiWebdavLocksUnlock/unlock.feature:97](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L97) +- [apiWebdavLocksUnlock/unlock.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L71) +- [apiWebdavLocksUnlock/unlock.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L76) +- [apiWebdavLocksUnlock/unlock.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L77) +- [apiWebdavLocksUnlock/unlock.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L91) +- [apiWebdavLocksUnlock/unlock.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L92) +- [apiWebdavLocksUnlock/unlock.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L116) +- [apiWebdavLocksUnlock/unlock.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L117) +- [apiWebdavLocksUnlock/unlock.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L118) - [apiWebdavLocksUnlock/unlock.feature:119](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L119) -- [apiWebdavLocksUnlock/unlock.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L120) -- [apiWebdavLocksUnlock/unlock.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L121) -- [apiWebdavLocksUnlock/unlock.feature:122](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L122) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L31) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L54) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L55) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:56](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L56) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L57) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L78) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L79) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L80) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L81) +- [apiWebdavLocksUnlock/unlock.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L124) +- [apiWebdavLocksUnlock/unlock.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L125) +- [apiWebdavLocksUnlock/unlock.feature:147](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L147) +- [apiWebdavLocksUnlock/unlock.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L148) +- [apiWebdavLocksUnlock/unlock.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L149) +- [apiWebdavLocksUnlock/unlock.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L150) +- [apiWebdavLocksUnlock/unlock.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L155) +- [apiWebdavLocksUnlock/unlock.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L156) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L28) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L29) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L30) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:97](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L97) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L31) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L44) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L45) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L60) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L61) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L62) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L63) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L68) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L69) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L90) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L91) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L92) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L93) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L98) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L99) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L100) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L124) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L125) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L126) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:127](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L127) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L115) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L116) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L117) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L118) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L131) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L132) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L148) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L149) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L150) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:151](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L151) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:152](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L152) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:153](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L153) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L174) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L175) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L176) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:177](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L177) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:164](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L164) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:165](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L165) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L180) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:181](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L181) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:182](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L182) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:183](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L183) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:188](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L188) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:189](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L189) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:210](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L210) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:211](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L211) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:212](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L212) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:213](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L213) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L218) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:219](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L219) ### Share File and sync features in a shared scenario @@ -638,8 +692,13 @@ _requires a [CS3 user provisioning api that can update the quota for a user](htt #### [not possible to move file into a received folder](https://github.com/owncloud/ocis/issues/764) - [apiShareOperationsToShares1/changingFilesShare.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L24) - [apiShareOperationsToShares1/changingFilesShare.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L25) -- [apiShareOperationsToShares1/changingFilesShare.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L82) -- [apiShareOperationsToShares1/changingFilesShare.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L98) +- [apiShareOperationsToShares1/changingFilesShare.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L30) +- [apiShareOperationsToShares1/changingFilesShare.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L115) +- [apiShareOperationsToShares1/changingFilesShare.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L116) +- [apiShareOperationsToShares1/changingFilesShare.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L121) +- [apiShareOperationsToShares1/changingFilesShare.feature:142](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L142) +- [apiShareOperationsToShares1/changingFilesShare.feature:143](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L143) +- [apiShareOperationsToShares1/changingFilesShare.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L148) Scenario Outline: Moving a file into a shared folder as the sharee and as the sharer - [apiWebdavMove2/moveFile.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFile.feature#L99) @@ -1047,6 +1106,22 @@ And other missing implementation of favorites - [apiWebdavEtagPropagation2/upload.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L111) - [apiWebdavEtagPropagation2/upload.feature:141](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L141) - [apiWebdavEtagPropagation2/upload.feature:171](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L171) +- [apiWebdavLocks2/resharedSharesToShares.feature:123](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L123) +- [apiWebdavLocks2/resharedSharesToShares.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L124) +- [apiWebdavLocks2/resharedSharesToShares.feature:152](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L152) +- [apiWebdavLocks2/resharedSharesToShares.feature:153](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L153) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L54) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L55) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:56](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L56) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L57) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L58) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L96) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:97](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L97) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L98) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L99) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L100) +- [apiShareOperationsToShares1/changingFilesShare.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L95) +- [apiShareOperationsToShares1/changingFilesShare.feature:169](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L169) #### [WWW-Authenticate header for unauthenticated requests is not clear](https://github.com/owncloud/ocis/issues/2285) - [apiWebdavOperations/refuseAccess.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L22) @@ -1238,10 +1313,13 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers #### [remote.php/dav/uploads endpoint does not exist](https://github.com/owncloud/ocis/issues/1321) - [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L20) - [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L21) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L34) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L35) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L71) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L72) +- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L26) +- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L39) +- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L40) +- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L45) +- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L81) +- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L82) +- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L87) #### [Blacklist files extensions](https://github.com/owncloud/ocis/issues/2177) - [apiWebdavProperties1/copyFile.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L133) @@ -1250,8 +1328,9 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers - [apiWebdavProperties1/createFolder.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L92) - [apiWebdavProperties1/createFolder.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L93) - [apiWebdavProperties1/createFolder.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L98) -- [apiWebdavUpload1/uploadFile.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L132) -- [apiWebdavUpload1/uploadFile.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L133) +- [apiWebdavUpload1/uploadFile.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L175) +- [apiWebdavUpload1/uploadFile.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L176) +- [apiWebdavUpload1/uploadFile.feature:181](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L181) - [apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature#L19) - [apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature#L35) - [apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature#L36) @@ -1303,8 +1382,10 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers #### [system configuration options missing](https://github.com/owncloud/ocis/issues/1323) - [apiWebdavUpload1/uploadFileToBlacklistedName.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L31) - [apiWebdavUpload1/uploadFileToBlacklistedName.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L32) -- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L66) -- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L67) +- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L37) +- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L71) +- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L72) +- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L77) #### moving a share from the /Shares jail to a user home is no longer supported. - [apiShareManagementToShares/mergeShare.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/mergeShare.feature#L89) @@ -1430,9 +1511,9 @@ _ocs: api compatibility, return correct status code_ - [apiWebdavProperties1/copyFile.feature:485](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L485) #### [downloading an old version of a file returns 501](https://github.com/owncloud/ocis/issues/2261) -- [apiVersions/fileVersions.feature:437](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersions.feature#L437) -- [apiVersions/fileVersions.feature:455](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersions.feature#L455) -- [apiVersions/fileVersionsSharingToShares.feature:305](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L305) +- [apiVersions/fileVersions.feature:426](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersions.feature#L426) +- [apiVersions/fileVersions.feature:444](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersions.feature#L444) +- [apiVersions/fileVersionsSharingToShares.feature:305](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L305) #### [file versions do not report the version author](https://github.com/owncloud/ocis/issues/2914) - [apiVersions/fileVersionAuthor.feature:14](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionAuthor.feature#L14) @@ -1513,5 +1594,11 @@ _ocs: api compatibility, return correct status code_ #### [Incorrect response while listing resources of a folder with depth infinity](https://github.com/owncloud/ocis/issues/3073) - [apiWebdavOperations/listFiles.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L128) +### [[spaces webdav] upload to a share that was locked by owner ends with status code 409](https://github.com/owncloud/ocis/issues/3128) +- [apiWebdavLocks2/resharedSharesToShares.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L41) +- [apiWebdavLocks2/resharedSharesToShares.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L42) +- [apiWebdavLocks2/resharedSharesToShares.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L73) +- [apiWebdavLocks2/resharedSharesToShares.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L74) + Note: always have an empty line at the end of this file. The bash script that processes this file may not process a scenario reference on the last line. diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index 6382f216057..a58b7b0bc8f 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -12,8 +12,8 @@ Basic file management like up and download, move, copy, properties, quota, trash - [apiTrashbin/trashbinFilesFolders.feature:231](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature#L231) #### [downloading an old version of a file returns 501](https://github.com/owncloud/ocis/issues/2261) -- [apiVersions/fileVersions.feature:437](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersions.feature#L437) -- [apiVersions/fileVersions.feature:455](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersions.feature#L455) +- [apiVersions/fileVersions.feature:426](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersions.feature#L426) +- [apiVersions/fileVersions.feature:444](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersions.feature#L444) - [apiVersions/fileVersionsSharingToShares.feature:305](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiVersions/fileVersionsSharingToShares.feature#L305) #### [file versions do not report the version author](https://github.com/owncloud/ocis/issues/2914) @@ -186,65 +186,78 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks/requestsWithToken.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L132) - [apiWebdavLocks/requestsWithToken.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L108) - [apiWebdavLocks/requestsWithToken.feature:109](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L109) -- [apiWebdavLocks/requestsWithToken.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#156) -- [apiWebdavLocks/requestsWithToken.feature:157](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#157) +- [apiWebdavLocks/requestsWithToken.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L156) +- [apiWebdavLocks/requestsWithToken.feature:157](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L157) - [apiWebdavLocks/requestsWithToken.feature:162](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks/requestsWithToken.feature#L162) - [apiWebdavLocks2/resharedSharesToShares.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L33) - [apiWebdavLocks2/resharedSharesToShares.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L34) - [apiWebdavLocks2/resharedSharesToShares.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L35) - [apiWebdavLocks2/resharedSharesToShares.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L36) -- [apiWebdavLocks2/resharedSharesToShares.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L58) -- [apiWebdavLocks2/resharedSharesToShares.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L59) -- [apiWebdavLocks2/resharedSharesToShares.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L60) -- [apiWebdavLocks2/resharedSharesToShares.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L78) -- [apiWebdavLocks2/resharedSharesToShares.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L79) -- [apiWebdavLocks2/resharedSharesToShares.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L61) -- [apiWebdavLocks2/resharedSharesToShares.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L80) -- [apiWebdavLocks2/resharedSharesToShares.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L81) -- [apiWebdavLocks2/resharedSharesToShares.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L101) -- [apiWebdavLocks2/resharedSharesToShares.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L102) -- [apiWebdavLocks2/resharedSharesToShares.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L103) -- [apiWebdavLocks2/resharedSharesToShares.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L104) -- [apiWebdavLocks2/resharedSharesToShares.feature:123](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L123) -- [apiWebdavLocks2/resharedSharesToShares.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L124) -- [apiWebdavLocks2/resharedSharesToShares.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L125) -- [apiWebdavLocks2/resharedSharesToShares.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L126) +- [apiWebdavLocks2/resharedSharesToShares.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L65) +- [apiWebdavLocks2/resharedSharesToShares.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L66) +- [apiWebdavLocks2/resharedSharesToShares.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L67) +- [apiWebdavLocks2/resharedSharesToShares.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L68) +- [apiWebdavLocks2/resharedSharesToShares.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L91) +- [apiWebdavLocks2/resharedSharesToShares.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L92) +- [apiWebdavLocks2/resharedSharesToShares.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L93) +- [apiWebdavLocks2/resharedSharesToShares.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L94) +- [apiWebdavLocks2/resharedSharesToShares.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L115) +- [apiWebdavLocks2/resharedSharesToShares.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L116) +- [apiWebdavLocks2/resharedSharesToShares.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L117) +- [apiWebdavLocks2/resharedSharesToShares.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L118) +- [apiWebdavLocks2/resharedSharesToShares.feature:144](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L144) +- [apiWebdavLocks2/resharedSharesToShares.feature:145](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L145) +- [apiWebdavLocks2/resharedSharesToShares.feature:146](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L146) +- [apiWebdavLocks2/resharedSharesToShares.feature:147](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L147) - [apiWebdavLocks2/setTimeout.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L32) - [apiWebdavLocks2/setTimeout.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L33) - [apiWebdavLocks2/setTimeout.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L34) - [apiWebdavLocks2/setTimeout.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L35) -- [apiWebdavLocks2/setTimeout.feature:56](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L56) -- [apiWebdavLocks2/setTimeout.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L57) -- [apiWebdavLocks2/setTimeout.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L58) -- [apiWebdavLocks2/setTimeout.feature:59](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L59) -- [apiWebdavLocks2/setTimeout.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L60) -- [apiWebdavLocks2/setTimeout.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L61) -- [apiWebdavLocks2/setTimeout.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L62) +- [apiWebdavLocks2/setTimeout.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L40) +- [apiWebdavLocks2/setTimeout.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L41) - [apiWebdavLocks2/setTimeout.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L63) - [apiWebdavLocks2/setTimeout.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L64) - [apiWebdavLocks2/setTimeout.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L65) -- [apiWebdavLocks2/setTimeout.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L89) -- [apiWebdavLocks2/setTimeout.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L90) -- [apiWebdavLocks2/setTimeout.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L91) -- [apiWebdavLocks2/setTimeout.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L92) -- [apiWebdavLocks2/setTimeout.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L93) -- [apiWebdavLocks2/setTimeout.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L94) -- [apiWebdavLocks2/setTimeout.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L95) -- [apiWebdavLocks2/setTimeout.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L96) -- [apiWebdavLocks2/setTimeout.feature:97](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L97) -- [apiWebdavLocks2/setTimeout.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L98) -- [apiWebdavLocks2/setTimeout.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L99) -- [apiWebdavLocks2/setTimeout.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L100) +- [apiWebdavLocks2/setTimeout.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L66) +- [apiWebdavLocks2/setTimeout.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L67) +- [apiWebdavLocks2/setTimeout.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L68) +- [apiWebdavLocks2/setTimeout.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L69) +- [apiWebdavLocks2/setTimeout.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L70) +- [apiWebdavLocks2/setTimeout.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L71) +- [apiWebdavLocks2/setTimeout.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L72) +- [apiWebdavLocks2/setTimeout.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L77) +- [apiWebdavLocks2/setTimeout.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L78) +- [apiWebdavLocks2/setTimeout.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L79) +- [apiWebdavLocks2/setTimeout.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L80) +- [apiWebdavLocks2/setTimeout.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L81) +- [apiWebdavLocks2/setTimeout.feature:105](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L105) +- [apiWebdavLocks2/setTimeout.feature:106](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L106) +- [apiWebdavLocks2/setTimeout.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L107) +- [apiWebdavLocks2/setTimeout.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L108) +- [apiWebdavLocks2/setTimeout.feature:109](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L109) +- [apiWebdavLocks2/setTimeout.feature:110](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L110) +- [apiWebdavLocks2/setTimeout.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L111) +- [apiWebdavLocks2/setTimeout.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L112) +- [apiWebdavLocks2/setTimeout.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L113) +- [apiWebdavLocks2/setTimeout.feature:114](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L114) +- [apiWebdavLocks2/setTimeout.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L115) +- [apiWebdavLocks2/setTimeout.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L116) +- [apiWebdavLocks2/setTimeout.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L121) +- [apiWebdavLocks2/setTimeout.feature:122](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L122) - [apiWebdavLocks2/setTimeout.feature:123](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L123) - [apiWebdavLocks2/setTimeout.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L124) - [apiWebdavLocks2/setTimeout.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L125) - [apiWebdavLocks2/setTimeout.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L126) -- [apiWebdavLocks2/setTimeout.feature:127](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L127) -- [apiWebdavLocks2/setTimeout.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L128) -- [apiWebdavLocks2/setTimeout.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L129) -- [apiWebdavLocks2/setTimeout.feature:130](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L130) -- [apiWebdavLocks2/setTimeout.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L131) -- [apiWebdavLocks2/setTimeout.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L132) +- [apiWebdavLocks2/setTimeout.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L149) +- [apiWebdavLocks2/setTimeout.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L150) +- [apiWebdavLocks2/setTimeout.feature:151](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L151) +- [apiWebdavLocks2/setTimeout.feature:152](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L152) +- [apiWebdavLocks2/setTimeout.feature:153](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L153) +- [apiWebdavLocks2/setTimeout.feature:154](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L154) +- [apiWebdavLocks2/setTimeout.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L155) +- [apiWebdavLocks2/setTimeout.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L156) +- [apiWebdavLocks2/setTimeout.feature:157](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L157) +- [apiWebdavLocks2/setTimeout.feature:158](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeout.feature#L158) - [apiWebdavLocks2/setTimeoutSharesToShares.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L40) - [apiWebdavLocks2/setTimeoutSharesToShares.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L41) - [apiWebdavLocks2/setTimeoutSharesToShares.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L42) @@ -255,96 +268,137 @@ Synchronization features like etag propagation, setting mtime and locking files - [apiWebdavLocks2/setTimeoutSharesToShares.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L47) - [apiWebdavLocks2/setTimeoutSharesToShares.feature:48](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L48) - [apiWebdavLocks2/setTimeoutSharesToShares.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L49) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L73) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L74) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L75) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L76) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L77) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L78) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L79) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L80) -- [apiWebdavLocks2/setTimeoutSharesToShares.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L81) - [apiWebdavLocks2/setTimeoutSharesToShares.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L82) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L83) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L84) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L85) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L86) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L87) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L88) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L89) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L90) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L91) - [apiWebdavLocks3/independentLocks.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L24) - [apiWebdavLocks3/independentLocks.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L25) - [apiWebdavLocks3/independentLocks.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L26) - [apiWebdavLocks3/independentLocks.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L27) -- [apiWebdavLocks3/independentLocks.feature:43](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L43) -- [apiWebdavLocks3/independentLocks.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L44) -- [apiWebdavLocks3/independentLocks.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L45) -- [apiWebdavLocks3/independentLocks.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L46) -- [apiWebdavLocks3/independentLocks.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L62) -- [apiWebdavLocks3/independentLocks.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L63) -- [apiWebdavLocks3/independentLocks.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L64) -- [apiWebdavLocks3/independentLocks.feature:65](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L65) +- [apiWebdavLocks3/independentLocks.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L32) +- [apiWebdavLocks3/independentLocks.feature:33](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L33) +- [apiWebdavLocks3/independentLocks.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L49) +- [apiWebdavLocks3/independentLocks.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L50) +- [apiWebdavLocks3/independentLocks.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L51) +- [apiWebdavLocks3/independentLocks.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L52) +- [apiWebdavLocks3/independentLocks.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L57) +- [apiWebdavLocks3/independentLocks.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L58) +- [apiWebdavLocks3/independentLocks.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L74) +- [apiWebdavLocks3/independentLocks.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L75) +- [apiWebdavLocks3/independentLocks.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L76) +- [apiWebdavLocks3/independentLocks.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L77) +- [apiWebdavLocks3/independentLocks.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L82) - [apiWebdavLocks3/independentLocks.feature:83](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L83) -- [apiWebdavLocks3/independentLocks.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L84) -- [apiWebdavLocks3/independentLocks.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L85) -- [apiWebdavLocks3/independentLocks.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L86) -- [apiWebdavLocks3/independentLocks.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L87) -- [apiWebdavLocks3/independentLocks.feature:88](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L88) -- [apiWebdavLocks3/independentLocks.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L89) -- [apiWebdavLocks3/independentLocks.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L90) +- [apiWebdavLocks3/independentLocks.feature:101](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L101) +- [apiWebdavLocks3/independentLocks.feature:102](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L102) +- [apiWebdavLocks3/independentLocks.feature:103](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L103) +- [apiWebdavLocks3/independentLocks.feature:104](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L104) +- [apiWebdavLocks3/independentLocks.feature:105](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L105) +- [apiWebdavLocks3/independentLocks.feature:106](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L106) +- [apiWebdavLocks3/independentLocks.feature:107](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L107) +- [apiWebdavLocks3/independentLocks.feature:108](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L108) +- [apiWebdavLocks3/independentLocks.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L113) +- [apiWebdavLocks3/independentLocks.feature:114](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L114) +- [apiWebdavLocks3/independentLocks.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L115) +- [apiWebdavLocks3/independentLocks.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocks.feature#L116) - [apiWebdavLocks3/independentLocksShareToShares.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L27) - [apiWebdavLocks3/independentLocksShareToShares.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L28) - [apiWebdavLocks3/independentLocksShareToShares.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L29) - [apiWebdavLocks3/independentLocksShareToShares.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L30) -- [apiWebdavLocks3/independentLocksShareToShares.feature:49](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L49) -- [apiWebdavLocks3/independentLocksShareToShares.feature:50](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L50) -- [apiWebdavLocks3/independentLocksShareToShares.feature:51](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L51) -- [apiWebdavLocks3/independentLocksShareToShares.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L52) -- [apiWebdavLocks3/independentLocksShareToShares.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L72) -- [apiWebdavLocks3/independentLocksShareToShares.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L73) -- [apiWebdavLocks3/independentLocksShareToShares.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L74) -- [apiWebdavLocks3/independentLocksShareToShares.feature:75](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L75) -- [apiWebdavLocks3/independentLocksShareToShares.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L94) -- [apiWebdavLocks3/independentLocksShareToShares.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L95) -- [apiWebdavLocks3/independentLocksShareToShares.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L96) -- [apiWebdavLocks3/independentLocksShareToShares.feature:97](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L97) -- [apiWebdavLocksUnlock/unlock.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L37) -- [apiWebdavLocksUnlock/unlock.feature:38](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L38) +- [apiWebdavLocks3/independentLocksShareToShares.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L35) +- [apiWebdavLocks3/independentLocksShareToShares.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L36) +- [apiWebdavLocks3/independentLocksShareToShares.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L55) +- [apiWebdavLocks3/independentLocksShareToShares.feature:56](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L56) +- [apiWebdavLocks3/independentLocksShareToShares.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L57) +- [apiWebdavLocks3/independentLocksShareToShares.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L58) +- [apiWebdavLocks3/independentLocksShareToShares.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L63) +- [apiWebdavLocks3/independentLocksShareToShares.feature:64](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L64) +- [apiWebdavLocks3/independentLocksShareToShares.feature:84](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L84) +- [apiWebdavLocks3/independentLocksShareToShares.feature:85](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L85) +- [apiWebdavLocks3/independentLocksShareToShares.feature:86](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L86) +- [apiWebdavLocks3/independentLocksShareToShares.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L87) +- [apiWebdavLocks3/independentLocksShareToShares.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L92) +- [apiWebdavLocks3/independentLocksShareToShares.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L93) +- [apiWebdavLocks3/independentLocksShareToShares.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L112) +- [apiWebdavLocks3/independentLocksShareToShares.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L113) +- [apiWebdavLocks3/independentLocksShareToShares.feature:114](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L114) +- [apiWebdavLocks3/independentLocksShareToShares.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L115) +- [apiWebdavLocks3/independentLocksShareToShares.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L120) +- [apiWebdavLocks3/independentLocksShareToShares.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks3/independentLocksShareToShares.feature#L121) +- [apiWebdavLocksUnlock/unlock.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L30) +- [apiWebdavLocksUnlock/unlock.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L31) +- [apiWebdavLocksUnlock/unlock.feature:46](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L46) +- [apiWebdavLocksUnlock/unlock.feature:47](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L47) - [apiWebdavLocksUnlock/unlock.feature:52](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L52) -- [apiWebdavLocksUnlock/unlock.feature:53](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L53) -- [apiWebdavLocksUnlock/unlock.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L54) -- [apiWebdavLocksUnlock/unlock.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L55) +- [apiWebdavLocksUnlock/unlock.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L68) - [apiWebdavLocksUnlock/unlock.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L69) - [apiWebdavLocksUnlock/unlock.feature:70](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L70) -- [apiWebdavLocksUnlock/unlock.feature:94](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L94) -- [apiWebdavLocksUnlock/unlock.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L95) -- [apiWebdavLocksUnlock/unlock.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L96) -- [apiWebdavLocksUnlock/unlock.feature:97](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L97) +- [apiWebdavLocksUnlock/unlock.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L71) +- [apiWebdavLocksUnlock/unlock.feature:76](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L76) +- [apiWebdavLocksUnlock/unlock.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L77) +- [apiWebdavLocksUnlock/unlock.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L91) +- [apiWebdavLocksUnlock/unlock.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L92) +- [apiWebdavLocksUnlock/unlock.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L116) +- [apiWebdavLocksUnlock/unlock.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L117) +- [apiWebdavLocksUnlock/unlock.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L118) - [apiWebdavLocksUnlock/unlock.feature:119](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L119) -- [apiWebdavLocksUnlock/unlock.feature:120](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L120) -- [apiWebdavLocksUnlock/unlock.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L121) -- [apiWebdavLocksUnlock/unlock.feature:122](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L122) +- [apiWebdavLocksUnlock/unlock.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L124) +- [apiWebdavLocksUnlock/unlock.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L125) +- [apiWebdavLocksUnlock/unlock.feature:147](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L147) +- [apiWebdavLocksUnlock/unlock.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L148) +- [apiWebdavLocksUnlock/unlock.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L149) +- [apiWebdavLocksUnlock/unlock.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L150) +- [apiWebdavLocksUnlock/unlock.feature:155](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L155) +- [apiWebdavLocksUnlock/unlock.feature:156](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlock.feature#L156) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L28) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L29) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L30) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L31) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L54) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L55) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:56](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L56) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L57) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:78](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L78) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:79](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L79) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:80](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L80) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L81) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:97](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L97) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:44](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L44) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L45) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:60](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L60) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:61](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L61) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:62](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L62) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:63](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L63) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:68](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L68) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:69](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L69) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:90](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L90) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L91) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L92) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L93) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L98) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L99) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L100) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L124) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:125](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L125) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:126](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L126) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:127](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L127) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L115) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L116) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L117) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:118](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L118) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L131) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L132) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L148) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L149) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:150](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L150) - [apiWebdavLocksUnlock/unlockSharingToShares.feature:151](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L151) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:152](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L152) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:153](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L153) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:174](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L174) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L175) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L176) -- [apiWebdavLocksUnlock/unlockSharingToShares.feature:177](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L177) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:164](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L164) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:165](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L165) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:180](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L180) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:181](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L181) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:182](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L182) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:183](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L183) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:188](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L188) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:189](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L189) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:210](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L210) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:211](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L211) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:212](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L212) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:213](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L213) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L218) +- [apiWebdavLocksUnlock/unlockSharingToShares.feature:219](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocksUnlock/unlockSharingToShares.feature#L219) ### Share File and sync features in a shared scenario @@ -628,8 +682,13 @@ _requires a [CS3 user provisioning api that can update the quota for a user](htt - [apiShareOperationsToShares1/changingFilesShare.feature:24](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L24) - [apiShareOperationsToShares1/changingFilesShare.feature:25](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L25) -- [apiShareOperationsToShares1/changingFilesShare.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L82) -- [apiShareOperationsToShares1/changingFilesShare.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L98) +- [apiShareOperationsToShares1/changingFilesShare.feature:30](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L30) +- [apiShareOperationsToShares1/changingFilesShare.feature:115](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L115) +- [apiShareOperationsToShares1/changingFilesShare.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L116) +- [apiShareOperationsToShares1/changingFilesShare.feature:121](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L121) +- [apiShareOperationsToShares1/changingFilesShare.feature:142](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L142) +- [apiShareOperationsToShares1/changingFilesShare.feature:143](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L143) +- [apiShareOperationsToShares1/changingFilesShare.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L148) Scenario Outline: Moving a file into a shared folder as the sharee and as the sharer - [apiWebdavMove2/moveFile.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavMove2/moveFile.feature#L99) @@ -1064,6 +1123,22 @@ And other missing implementation of favorites - [apiWebdavEtagPropagation2/upload.feature:111](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L111) - [apiWebdavEtagPropagation2/upload.feature:141](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L141) - [apiWebdavEtagPropagation2/upload.feature:171](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavEtagPropagation2/upload.feature#L171) +- [apiWebdavLocks2/resharedSharesToShares.feature:123](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L123) +- [apiWebdavLocks2/resharedSharesToShares.feature:124](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L124) +- [apiWebdavLocks2/resharedSharesToShares.feature:152](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L152) +- [apiWebdavLocks2/resharedSharesToShares.feature:153](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L153) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:54](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L54) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:55](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L55) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:56](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L56) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:57](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L57) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:58](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L58) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:96](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L96) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:97](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L97) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L98) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:99](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L99) +- [apiWebdavLocks2/setTimeoutSharesToShares.feature:100](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/setTimeoutSharesToShares.feature#L100) +- [apiShareOperationsToShares1/changingFilesShare.feature:95](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L95) +- [apiShareOperationsToShares1/changingFilesShare.feature:169](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareOperationsToShares1/changingFilesShare.feature#L169) #### [WWW-Authenticate header for unauthenticated requests is not clear](https://github.com/owncloud/ocis/issues/2285) - [apiWebdavOperations/refuseAccess.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/refuseAccess.feature#L22) @@ -1255,10 +1330,13 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers #### [remote.php/dav/uploads endpoint does not exist](https://github.com/owncloud/ocis/issues/1321) - [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:20](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L20) - [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L21) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:34](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L34) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L35) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L71) -- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L72) +- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L26) +- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:39](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L39) +- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:40](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L40) +- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:45](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L45) +- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:81](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L81) +- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:82](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L82) +- [apiWebdavUpload1/uploadFileToExcludedDirectory.feature:87](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToExcludedDirectory.feature#L87) #### [Blacklist files extensions](https://github.com/owncloud/ocis/issues/2177) - [apiWebdavProperties1/copyFile.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/copyFile.feature#L133) @@ -1267,8 +1345,9 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers - [apiWebdavProperties1/createFolder.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L92) - [apiWebdavProperties1/createFolder.feature:93](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L93) - [apiWebdavProperties1/createFolder.feature:98](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavProperties1/createFolder.feature#L98) -- [apiWebdavUpload1/uploadFile.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L132) -- [apiWebdavUpload1/uploadFile.feature:133](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L133) +- [apiWebdavUpload1/uploadFile.feature:175](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L175) +- [apiWebdavUpload1/uploadFile.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L176) +- [apiWebdavUpload1/uploadFile.feature:181](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFile.feature#L181) - [apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature:19](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature#L19) - [apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature#L35) - [apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature:36](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload2/uploadFileToBlacklistedNameUsingOldChunking.feature#L36) @@ -1320,8 +1399,10 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers #### [system configuration options missing](https://github.com/owncloud/ocis/issues/1323) - [apiWebdavUpload1/uploadFileToBlacklistedName.feature:31](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L31) - [apiWebdavUpload1/uploadFileToBlacklistedName.feature:32](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L32) -- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:66](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L66) -- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:67](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L67) +- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:37](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L37) +- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:71](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L71) +- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:72](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L72) +- [apiWebdavUpload1/uploadFileToBlacklistedName.feature:77](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavUpload1/uploadFileToBlacklistedName.feature#L77) #### moving a share from the /Shares jail to a user home is no longer supported. - [apiShareManagementToShares/mergeShare.feature:89](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementToShares/mergeShare.feature#L89) @@ -1514,5 +1595,11 @@ _ocs: api compatibility, return correct status code_ #### [Incorrect response while listing resources of a folder with depth infinity](https://github.com/owncloud/ocis/issues/3073) - [apiWebdavOperations/listFiles.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/listFiles.feature#L128) +### [[spaces webdav] upload to a share that was locked by owner ends with status code 409](https://github.com/owncloud/ocis/issues/3128) +- [apiWebdavLocks2/resharedSharesToShares.feature:41](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L41) +- [apiWebdavLocks2/resharedSharesToShares.feature:42](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L42) +- [apiWebdavLocks2/resharedSharesToShares.feature:73](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L73) +- [apiWebdavLocks2/resharedSharesToShares.feature:74](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavLocks2/resharedSharesToShares.feature#L74) + Note: always have an empty line at the end of this file. The bash script that processes this file may not process a scenario reference on the last line. From e55c1157726dc7b6229eabdc4b57a384dcd30c48 Mon Sep 17 00:00:00 2001 From: Michael Barz Date: Fri, 11 Feb 2022 13:18:51 +0100 Subject: [PATCH 47/49] Add space props (#2527) * add space properties * add changelog * handle write error * optimize and send back the updated space * add permission checks --- .../unreleased/change-space-attributes.md | 5 + pkg/storage/utils/decomposedfs/spaces.go | 117 ++++++++++++++++-- .../utils/decomposedfs/xattrs/xattrs.go | 5 +- 3 files changed, 116 insertions(+), 11 deletions(-) create mode 100644 changelog/unreleased/change-space-attributes.md diff --git a/changelog/unreleased/change-space-attributes.md b/changelog/unreleased/change-space-attributes.md new file mode 100644 index 00000000000..250c2d36dd7 --- /dev/null +++ b/changelog/unreleased/change-space-attributes.md @@ -0,0 +1,5 @@ +Change: Store space attributes in decomposedFS + +We need to store more space attributes in the storage. This implements extended space attributes in the decomposedFS + +https://github.com/cs3org/reva/pull/2527 \ No newline at end of file diff --git a/pkg/storage/utils/decomposedfs/spaces.go b/pkg/storage/utils/decomposedfs/spaces.go index 957cd3ce2d4..b847bd3ce4e 100644 --- a/pkg/storage/utils/decomposedfs/spaces.go +++ b/pkg/storage/utils/decomposedfs/spaces.go @@ -387,21 +387,62 @@ func (fs *Decomposedfs) UpdateStorageSpace(ctx context.Context, req *provider.Up } space.Owner = u + metadata := make(map[string]string, 5) if space.Name != "" { - if err := node.SetMetadata(xattrs.SpaceNameAttr, space.Name); err != nil { - return nil, err - } + metadata[xattrs.SpaceNameAttr] = space.Name } if space.Quota != nil { - if err := node.SetMetadata(xattrs.QuotaAttr, strconv.FormatUint(space.Quota.QuotaMaxBytes, 10)); err != nil { - return nil, err + metadata[xattrs.QuotaAttr] = strconv.FormatUint(space.Quota.QuotaMaxBytes, 10) + } + + // TODO also return values which are not in the request + hasDescription := false + if space.Opaque != nil { + if description, ok := space.Opaque.Map["description"]; ok { + metadata[xattrs.SpaceDescriptionAttr] = string(description.Value) + hasDescription = true + } + if image, ok := space.Opaque.Map["image"]; ok { + metadata[xattrs.SpaceImageAttr] = string(image.Value) + } + if readme, ok := space.Opaque.Map["readme"]; ok { + metadata[xattrs.SpaceReadmeAttr] = string(readme.Value) } } + // TODO change the permission handling + // these two attributes need manager permissions + if space.Name != "" || hasDescription { + err = fs.checkManagerPermission(ctx, node) + } + if err != nil { + return &provider.UpdateStorageSpaceResponse{ + Status: &v1beta11.Status{Code: v1beta11.Code_CODE_PERMISSION_DENIED, Message: err.Error()}, + }, nil + } + // all other attributes need editor permissions + err = fs.checkEditorPermission(ctx, node) + if err != nil { + return &provider.UpdateStorageSpaceResponse{ + Status: &v1beta11.Status{Code: v1beta11.Code_CODE_PERMISSION_DENIED, Message: err.Error()}, + }, nil + } + + err = xattrs.SetMultiple(node.InternalPath(), metadata) + if err != nil { + return nil, err + } + + // send back the updated data from the storage + updatedSpace, err := fs.storageSpaceFromNode(ctx, node, "*", node.InternalPath(), false) + if err != nil { + return nil, err + } + return &provider.UpdateStorageSpaceResponse{ Status: &v1beta11.Status{Code: v1beta11.Code_CODE_OK}, - StorageSpace: space, + StorageSpace: updatedSpace, }, nil } @@ -634,15 +675,20 @@ func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, } } + spaceAttributes, err := xattrs.All(nodePath) + if err != nil { + return nil, err + } + // quota - v, err := xattrs.Get(nodePath, xattrs.QuotaAttr) - if err == nil { + quotaAttr, ok := spaceAttributes[xattrs.QuotaAttr] + if ok { // make sure we have a proper signed int // we use the same magic numbers to indicate: // -1 = uncalculated // -2 = unknown // -3 = unlimited - if quota, err := strconv.ParseUint(v, 10, 64); err == nil { + if quota, err := strconv.ParseUint(quotaAttr, 10, 64); err == nil { space.Quota = &provider.Quota{ QuotaMaxBytes: quota, QuotaMaxFiles: math.MaxUint64, // TODO MaxUInt64? = unlimited? why even max files? 0 = unlimited? @@ -651,6 +697,57 @@ func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node, return nil, err } } - + spaceImage, ok := spaceAttributes[xattrs.SpaceImageAttr] + if ok { + space.Opaque.Map["image"] = &types.OpaqueEntry{ + Decoder: "plain", + Value: []byte(spaceImage), + } + } + spaceDescription, ok := spaceAttributes[xattrs.SpaceDescriptionAttr] + if ok { + space.Opaque.Map["description"] = &types.OpaqueEntry{ + Decoder: "plain", + Value: []byte(spaceDescription), + } + } + spaceReadme, ok := spaceAttributes[xattrs.SpaceReadmeAttr] + if ok { + space.Opaque.Map["readme"] = &types.OpaqueEntry{ + Decoder: "plain", + Value: []byte(spaceReadme), + } + } return space, nil } + +func (fs *Decomposedfs) checkManagerPermission(ctx context.Context, n *node.Node) error { + // to update the space name or short description we need the manager role + // current workaround: check if AddGrant Permission exists + managerPerm, err := fs.p.HasPermission(ctx, n, func(rp *provider.ResourcePermissions) bool { + return rp.AddGrant + }) + switch { + case err != nil: + return errtypes.InternalError(err.Error()) + case !managerPerm: + msg := fmt.Sprintf("not enough permissions to change attributes on %s", filepath.Join(n.ParentID, n.Name)) + return errtypes.PermissionDenied(msg) + } + return nil +} + +func (fs *Decomposedfs) checkEditorPermission(ctx context.Context, n *node.Node) error { + // current workaround: check if InitiateFileUpload Permission exists + editorPerm, err := fs.p.HasPermission(ctx, n, func(rp *provider.ResourcePermissions) bool { + return rp.InitiateFileUpload + }) + switch { + case err != nil: + return errtypes.InternalError(err.Error()) + case !editorPerm: + msg := fmt.Sprintf("not enough permissions to change attributes on %s", filepath.Join(n.ParentID, n.Name)) + return errtypes.PermissionDenied(msg) + } + return nil +} diff --git a/pkg/storage/utils/decomposedfs/xattrs/xattrs.go b/pkg/storage/utils/decomposedfs/xattrs/xattrs.go index f65900c202a..2f7b3f19421 100644 --- a/pkg/storage/utils/decomposedfs/xattrs/xattrs.go +++ b/pkg/storage/utils/decomposedfs/xattrs/xattrs.go @@ -83,7 +83,10 @@ const ( QuotaAttr string = OcisPrefix + "quota" // the name given to a storage space. It should not contain any semantics as its only purpose is to be read. - SpaceNameAttr string = OcisPrefix + "space.name" + SpaceNameAttr string = OcisPrefix + "space.name" + SpaceDescriptionAttr string = OcisPrefix + "space.description" + SpaceReadmeAttr string = OcisPrefix + "space.readme" + SpaceImageAttr string = OcisPrefix + "space.image" UserAcePrefix string = "u:" GroupAcePrefix string = "g:" From 5832a9b134a9e2b8e675e3a3e7d9093346c0ae2a Mon Sep 17 00:00:00 2001 From: Michael Barz Date: Mon, 14 Feb 2022 10:12:47 +0100 Subject: [PATCH 48/49] Use space description on creation (#2533) * fix bug, coowners are now managers * choose space description on creation * add changelog Signed-off-by: Michael Barz * fix unit tests --- ...e-use-description-during-space-creation.md | 5 ++ .../ocs/conversions/permissions_test.go | 2 +- .../services/owncloud/ocs/conversions/role.go | 2 +- pkg/storage/utils/decomposedfs/spaces.go | 53 ++++++++++--------- 4 files changed, 36 insertions(+), 26 deletions(-) create mode 100644 changelog/unreleased/change-use-description-during-space-creation.md diff --git a/changelog/unreleased/change-use-description-during-space-creation.md b/changelog/unreleased/change-use-description-during-space-creation.md new file mode 100644 index 00000000000..0045dda01d2 --- /dev/null +++ b/changelog/unreleased/change-use-description-during-space-creation.md @@ -0,0 +1,5 @@ +Change: Use description during space creation + +We can now use a space description during space creation. We also fixed a bug in the spaces roles. Co-owners are now maintainers. + +https://github.com/cs3org/reva/pull/2524 diff --git a/internal/http/services/owncloud/ocs/conversions/permissions_test.go b/internal/http/services/owncloud/ocs/conversions/permissions_test.go index 6604b8deb9e..ec09a32b7b7 100644 --- a/internal/http/services/owncloud/ocs/conversions/permissions_test.go +++ b/internal/http/services/owncloud/ocs/conversions/permissions_test.go @@ -145,7 +145,7 @@ func TestPermissions2Role(t *testing.T) { table := map[Permissions]string{ PermissionRead: RoleViewer, PermissionRead | PermissionWrite | PermissionCreate | PermissionDelete: RoleEditor, - PermissionAll: RoleCoowner, + PermissionAll: RoleManager, PermissionWrite: RoleLegacy, PermissionShare: RoleLegacy, PermissionWrite | PermissionShare: RoleLegacy, diff --git a/internal/http/services/owncloud/ocs/conversions/role.go b/internal/http/services/owncloud/ocs/conversions/role.go index f04bd7cabdd..94407560401 100644 --- a/internal/http/services/owncloud/ocs/conversions/role.go +++ b/internal/http/services/owncloud/ocs/conversions/role.go @@ -289,7 +289,7 @@ func RoleFromOCSPermissions(p Permissions) *Role { if p.Contain(PermissionRead) { if p.Contain(PermissionWrite) && p.Contain(PermissionCreate) && p.Contain(PermissionDelete) { if p.Contain(PermissionShare) { - return NewCoownerRole() + return NewManagerRole() } return NewEditorRole() } diff --git a/pkg/storage/utils/decomposedfs/spaces.go b/pkg/storage/utils/decomposedfs/spaces.go index b847bd3ce4e..4d5d642bb07 100644 --- a/pkg/storage/utils/decomposedfs/spaces.go +++ b/pkg/storage/utils/decomposedfs/spaces.go @@ -67,6 +67,13 @@ func (fs *Decomposedfs) CreateStorageSpace(ctx context.Context, req *provider.Cr spaceID = string(e.Value) } } + // allow sending a space description + var description string + if req.Opaque != nil && req.Opaque.Map != nil { + if e, ok := req.Opaque.Map["description"]; ok && e.Decoder == "plain" { + description = string(e.Value) + } + } // TODO enforce a uuid? // TODO clarify if we want to enforce a single personal storage space or if we want to allow sending the spaceid if req.Type == "personal" { @@ -111,40 +118,27 @@ func (fs *Decomposedfs) CreateStorageSpace(ctx context.Context, req *provider.Cr return nil, err } + metadata := make(map[string]string, 3) if q := req.GetQuota(); q != nil { // set default space quota - if err := n.SetMetadata(xattrs.QuotaAttr, strconv.FormatUint(q.QuotaMaxBytes, 10)); err != nil { - return nil, err - } + metadata[xattrs.QuotaAttr] = strconv.FormatUint(q.QuotaMaxBytes, 10) } - if err := n.SetMetadata(xattrs.SpaceNameAttr, req.Name); err != nil { - return nil, err + metadata[xattrs.SpaceNameAttr] = req.Name + if description != "" { + metadata[xattrs.SpaceDescriptionAttr] = description } - - resp := &provider.CreateStorageSpaceResponse{ - Status: &v1beta11.Status{ - Code: v1beta11.Code_CODE_OK, - }, - StorageSpace: &provider.StorageSpace{ - Owner: u, - Id: &provider.StorageSpaceId{ - OpaqueId: spaceID, - }, - Root: &provider.ResourceId{ - StorageId: spaceID, - OpaqueId: spaceID, - }, - Name: req.GetName(), - Quota: req.GetQuota(), - SpaceType: req.GetType(), - }, + if err := xattrs.SetMultiple(n.InternalPath(), metadata); err != nil { + return nil, err } ctx = context.WithValue(ctx, utils.SpaceGrant, struct{}{}) if err := fs.AddGrant(ctx, &provider.Reference{ - ResourceId: resp.StorageSpace.Root, + ResourceId: &provider.ResourceId{ + StorageId: spaceID, + OpaqueId: spaceID, + }, }, &provider.Grant{ Grantee: &provider.Grantee{ Type: provider.GranteeType_GRANTEE_TYPE_USER, @@ -157,6 +151,17 @@ func (fs *Decomposedfs) CreateStorageSpace(ctx context.Context, req *provider.Cr return nil, err } + space, err := fs.storageSpaceFromNode(ctx, n, "*", n.InternalPath(), false) + if err != nil { + return nil, err + } + + resp := &provider.CreateStorageSpaceResponse{ + Status: &v1beta11.Status{ + Code: v1beta11.Code_CODE_OK, + }, + StorageSpace: space, + } return resp, nil } From c7e66072ef4632ce5bba6b94f697205d62e15846 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Mon, 14 Feb 2022 10:13:45 +0100 Subject: [PATCH 49/49] decomposedfs: refactor xattrs package errors (#2540) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../unreleased/decomposedfs-xattr-errors.md | 5 +++ pkg/storage/utils/decomposedfs/node/node.go | 26 ++++++------- .../utils/decomposedfs/node/permissions.go | 15 +------- .../decomposedfs/node/permissions_darwin.go | 37 ------------------- .../permissions_unix.go => xattrs/errors.go} | 19 ++++++++-- .../utils/decomposedfs/xattrs/xattrs.go | 14 +++++++ 6 files changed, 49 insertions(+), 67 deletions(-) create mode 100644 changelog/unreleased/decomposedfs-xattr-errors.md delete mode 100644 pkg/storage/utils/decomposedfs/node/permissions_darwin.go rename pkg/storage/utils/decomposedfs/{node/permissions_unix.go => xattrs/errors.go} (60%) diff --git a/changelog/unreleased/decomposedfs-xattr-errors.md b/changelog/unreleased/decomposedfs-xattr-errors.md new file mode 100644 index 00000000000..3b21b1baf80 --- /dev/null +++ b/changelog/unreleased/decomposedfs-xattr-errors.md @@ -0,0 +1,5 @@ +Enhancement: Refactored the xattrs package in the decomposedfs + +The xattrs package now uses the xattr.ENOATTR instead of os.ENODATA or os.ENOATTR to check attribute existence. + +https://github.com/cs3org/reva/pull/2540 diff --git a/pkg/storage/utils/decomposedfs/node/node.go b/pkg/storage/utils/decomposedfs/node/node.go index 6368da6e8e8..dfa428c46e7 100644 --- a/pkg/storage/utils/decomposedfs/node/node.go +++ b/pkg/storage/utils/decomposedfs/node/node.go @@ -181,9 +181,9 @@ func ReadNode(ctx context.Context, lu PathLookup, id string) (n *Node, err error switch { case err == nil: n.ParentID = attr - case isAttrUnset(err): + case xattrs.IsAttrUnset(err): return nil, errtypes.InternalError(err.Error()) - case isNotFound(err): + case xattrs.IsNotExist(err): return n, nil // swallow not found, the node defaults to exists = false default: return nil, errtypes.InternalError(err.Error()) @@ -216,7 +216,7 @@ func ReadNode(ctx context.Context, lu PathLookup, id string) (n *Node, err error // Check if parent exists. Otherwise this node is part of a deleted subtree _, err = os.Stat(lu.InternalPath(n.ParentID)) if err != nil { - if isNotFound(err) { + if os.IsNotExist(err) { return nil, errtypes.NotFound(err.Error()) } return nil, err @@ -320,7 +320,7 @@ func (n *Node) Owner() (*userpb.UserId, error) { switch { case err == nil: owner.OpaqueId = attr - case isAttrUnset(err), isNotFound(err): + case xattrs.IsAttrUnset(err), xattrs.IsNotExist(err): fallthrough default: return nil, err @@ -331,7 +331,7 @@ func (n *Node) Owner() (*userpb.UserId, error) { switch { case err == nil: owner.Idp = attr - case isAttrUnset(err), isNotFound(err): + case xattrs.IsAttrUnset(err), xattrs.IsNotExist(err): fallthrough default: return nil, err @@ -342,7 +342,7 @@ func (n *Node) Owner() (*userpb.UserId, error) { switch { case err == nil: owner.Type = utils.UserTypeMap(attr) - case isAttrUnset(err), isNotFound(err): + case xattrs.IsAttrUnset(err), xattrs.IsNotExist(err): fallthrough default: // TODO the user type defaults to invalid, which is the case @@ -681,9 +681,9 @@ func readChecksumIntoResourceChecksum(ctx context.Context, nodePath, algo string Type: storageprovider.PKG2GRPCXS(algo), Sum: hex.EncodeToString([]byte(v)), } - case isAttrUnset(err): + case xattrs.IsAttrUnset(err): appctx.GetLogger(ctx).Debug().Err(err).Str("nodepath", nodePath).Str("algorithm", algo).Msg("checksum not set") - case isNotFound(err): + case xattrs.IsNotExist(err): appctx.GetLogger(ctx).Error().Err(err).Str("nodepath", nodePath).Str("algorithm", algo).Msg("file not fount") default: appctx.GetLogger(ctx).Error().Err(err).Str("nodepath", nodePath).Str("algorithm", algo).Msg("could not read checksum") @@ -703,9 +703,9 @@ func readChecksumIntoOpaque(ctx context.Context, nodePath, algo string, ri *prov Decoder: "plain", Value: []byte(hex.EncodeToString([]byte(v))), } - case isAttrUnset(err): + case xattrs.IsAttrUnset(err): appctx.GetLogger(ctx).Debug().Err(err).Str("nodepath", nodePath).Str("algorithm", algo).Msg("checksum not set") - case isNotFound(err): + case xattrs.IsNotExist(err): appctx.GetLogger(ctx).Error().Err(err).Str("nodepath", nodePath).Str("algorithm", algo).Msg("file not fount") default: appctx.GetLogger(ctx).Error().Err(err).Str("nodepath", nodePath).Str("algorithm", algo).Msg("could not read checksum") @@ -735,9 +735,9 @@ func readQuotaIntoOpaque(ctx context.Context, nodePath string, ri *provider.Reso } else { appctx.GetLogger(ctx).Error().Err(err).Str("nodepath", nodePath).Str("quota", v).Msg("malformed quota") } - case isAttrUnset(err): + case xattrs.IsAttrUnset(err): appctx.GetLogger(ctx).Debug().Err(err).Str("nodepath", nodePath).Msg("quota not set") - case isNotFound(err): + case xattrs.IsNotExist(err): appctx.GetLogger(ctx).Error().Err(err).Str("nodepath", nodePath).Msg("file not found when reading quota") default: appctx.GetLogger(ctx).Error().Err(err).Str("nodepath", nodePath).Msg("could not read quota") @@ -862,7 +862,7 @@ func (n *Node) ReadUserPermissions(ctx context.Context, u *userpb.User) (ap prov switch { case err == nil: AddPermissions(&ap, g.GetPermissions()) - case isAttrUnset(err): + case xattrs.IsAttrUnset(err): err = nil appctx.GetLogger(ctx).Error().Interface("node", n).Str("grant", grantees[i]).Interface("grantees", grantees).Msg("grant vanished from node after listing") // continue with next segment diff --git a/pkg/storage/utils/decomposedfs/node/permissions.go b/pkg/storage/utils/decomposedfs/node/permissions.go index 4622045278c..24d06ef9763 100644 --- a/pkg/storage/utils/decomposedfs/node/permissions.go +++ b/pkg/storage/utils/decomposedfs/node/permissions.go @@ -21,7 +21,6 @@ package node import ( "context" "strings" - "syscall" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" @@ -30,7 +29,6 @@ import ( "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/xattrs" "github.com/cs3org/reva/pkg/utils" "github.com/pkg/errors" - "github.com/pkg/xattr" ) // NoPermissions represents an empty set of permissions @@ -252,7 +250,7 @@ func nodeHasPermission(ctx context.Context, cn *Node, groupsMap map[string]bool, if check(g.GetPermissions()) { return true } - case isAttrUnset(err): + case xattrs.IsAttrUnset(err): appctx.GetLogger(ctx).Error().Interface("node", cn.ID).Str("grant", grantees[i]).Interface("grantees", grantees).Msg("grant vanished from node after listing") default: appctx.GetLogger(ctx).Error().Err(err).Interface("node", cn.ID).Str("grant", grantees[i]).Msg("error reading permissions") @@ -290,14 +288,3 @@ func (p *Permissions) getUserAndPermissions(ctx context.Context, n *Node) (*user } return u, nil } - -// The os not exists error is buried inside the xattr error, -// so we cannot just use os.IsNotExists(). -func isNotFound(err error) bool { - if xerr, ok := err.(*xattr.Error); ok { - if serr, ok2 := xerr.Err.(syscall.Errno); ok2 { - return serr == syscall.ENOENT - } - } - return false -} diff --git a/pkg/storage/utils/decomposedfs/node/permissions_darwin.go b/pkg/storage/utils/decomposedfs/node/permissions_darwin.go deleted file mode 100644 index a44b30df194..00000000000 --- a/pkg/storage/utils/decomposedfs/node/permissions_darwin.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2018-2021 CERN -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// In applying this license, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -//go:build darwin -// +build darwin - -package node - -import ( - "syscall" - - "github.com/pkg/xattr" -) - -func isAttrUnset(err error) bool { - if xerr, ok := err.(*xattr.Error); ok { - if serr, ok2 := xerr.Err.(syscall.Errno); ok2 { - return serr == syscall.ENOATTR - } - } - return false -} diff --git a/pkg/storage/utils/decomposedfs/node/permissions_unix.go b/pkg/storage/utils/decomposedfs/xattrs/errors.go similarity index 60% rename from pkg/storage/utils/decomposedfs/node/permissions_unix.go rename to pkg/storage/utils/decomposedfs/xattrs/errors.go index d10efca03a9..fb91cebd4c8 100644 --- a/pkg/storage/utils/decomposedfs/node/permissions_unix.go +++ b/pkg/storage/utils/decomposedfs/xattrs/errors.go @@ -19,7 +19,7 @@ //go:build !darwin // +build !darwin -package node +package xattrs import ( "syscall" @@ -27,10 +27,23 @@ import ( "github.com/pkg/xattr" ) -func isAttrUnset(err error) bool { +// IsNotExist checks if there is a os not exists error buried inside the xattr error, +// as we cannot just use os.IsNotExist(). +func IsNotExist(err error) bool { if xerr, ok := err.(*xattr.Error); ok { if serr, ok2 := xerr.Err.(syscall.Errno); ok2 { - return serr == syscall.ENODATA + return serr == syscall.ENOENT + } + } + return false +} + +// IsAttrUnset checks the xattr.ENOATTR from the xattr package which redifines it as ENODATA on platforms that do not natively support it (eg. linux) +// see https://github.com/pkg/xattr/blob/8725d4ccc0fcef59c8d9f0eaf606b3c6f962467a/xattr_linux.go#L19-L22 +func IsAttrUnset(err error) bool { + if xerr, ok := err.(*xattr.Error); ok { + if serr, ok2 := xerr.Err.(syscall.Errno); ok2 { + return serr == xattr.ENOATTR } } return false diff --git a/pkg/storage/utils/decomposedfs/xattrs/xattrs.go b/pkg/storage/utils/decomposedfs/xattrs/xattrs.go index 2f7b3f19421..b9b14b66366 100644 --- a/pkg/storage/utils/decomposedfs/xattrs/xattrs.go +++ b/pkg/storage/utils/decomposedfs/xattrs/xattrs.go @@ -19,6 +19,7 @@ package xattrs import ( + "strconv" "strings" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" @@ -165,6 +166,19 @@ func Get(filePath, key string) (string, error) { return val, nil } +// GetInt64 reads a string as int64 from the xattrs +func GetInt64(filePath, key string) (int64, error) { + attr, err := Get(filePath, key) + if err != nil { + return 0, err + } + v, err := strconv.ParseInt(attr, 10, 64) + if err != nil { + return 0, errors.Wrapf(err, "invalid xattr format") + } + return v, nil +} + // All reads all extended attributes for a node func All(filePath string) (map[string]string, error) { attrNames, err := xattr.List(filePath)