From 11cc78add8ba06a70c0515aa762f07d1ebd65ab7 Mon Sep 17 00:00:00 2001 From: Andre Duffeck Date: Thu, 13 Oct 2022 10:07:08 +0200 Subject: [PATCH] Add space owner to events (#3340) * Add a SpaceOwner to some of the events alongside the executant The SpaceOwner is a user that can be used by consumers of the event to access the space that was affected by the event. * Add SpaceOwnerOrManager() which returns the space owner or a manager * Send the space owner through the special chan as soon as it's known * Also send the space owner with the FileUploaded events * Add changelog --- .../unreleased/add-spaceowner-to-events.md | 6 +++ .../eventsmiddleware/conversion.go | 43 ++++++++++------- .../interceptors/eventsmiddleware/events.go | 24 +++++++--- pkg/events/files.go | 39 ++++++++------- pkg/rhttp/datatx/datatx.go | 9 ++-- pkg/rhttp/datatx/manager/simple/simple.go | 4 +- pkg/rhttp/datatx/manager/spaces/spaces.go | 4 +- pkg/rhttp/datatx/manager/tus/tus.go | 5 +- pkg/storage/fs/owncloudsql/upload.go | 5 +- pkg/storage/storage.go | 2 +- .../utils/decomposedfs/decomposedfs.go | 13 +++++ pkg/storage/utils/decomposedfs/metadata.go | 7 +++ pkg/storage/utils/decomposedfs/node/node.go | 21 +++++++++ .../utils/decomposedfs/node/node_test.go | 15 ++++++ pkg/storage/utils/decomposedfs/recycle.go | 4 ++ pkg/storage/utils/decomposedfs/revisions.go | 4 ++ pkg/storage/utils/decomposedfs/upload.go | 17 ++++--- pkg/storage/utils/localfs/upload.go | 5 +- pkg/storagespace/context.go | 47 +++++++++++++++++++ 19 files changed, 216 insertions(+), 58 deletions(-) create mode 100644 changelog/unreleased/add-spaceowner-to-events.md create mode 100644 pkg/storagespace/context.go diff --git a/changelog/unreleased/add-spaceowner-to-events.md b/changelog/unreleased/add-spaceowner-to-events.md new file mode 100644 index 0000000000..4650d5b4aa --- /dev/null +++ b/changelog/unreleased/add-spaceowner-to-events.md @@ -0,0 +1,6 @@ +Enhancement: Add SpaceOwner to some event + +We added a SpaceOwner field to some of the events which can be used by +consumers to gain access to the affected space. + +https://github.com/cs3org/reva/pull/3340 diff --git a/internal/grpc/interceptors/eventsmiddleware/conversion.go b/internal/grpc/interceptors/eventsmiddleware/conversion.go index bc5305b652..b6f3a86f8c 100644 --- a/internal/grpc/interceptors/eventsmiddleware/conversion.go +++ b/internal/grpc/interceptors/eventsmiddleware/conversion.go @@ -28,10 +28,11 @@ import ( ) // ContainerCreated converts the response to an event -func ContainerCreated(r *provider.CreateContainerResponse, req *provider.CreateContainerRequest, executant *user.UserId) events.ContainerCreated { +func ContainerCreated(r *provider.CreateContainerResponse, req *provider.CreateContainerRequest, spaceOwner, executant *user.UserId) events.ContainerCreated { return events.ContainerCreated{ - Executant: executant, - Ref: req.Ref, + SpaceOwner: spaceOwner, + Executant: executant, + Ref: req.Ref, } } @@ -166,18 +167,20 @@ func LinkRemoved(r *link.RemovePublicShareResponse, req *link.RemovePublicShareR } // FileTouched converts the response to an event -func FileTouched(r *provider.TouchFileResponse, req *provider.TouchFileRequest, executant *user.UserId) events.FileTouched { +func FileTouched(r *provider.TouchFileResponse, req *provider.TouchFileRequest, spaceOwner, executant *user.UserId) events.FileTouched { return events.FileTouched{ - Executant: executant, - Ref: req.Ref, + SpaceOwner: spaceOwner, + Executant: executant, + Ref: req.Ref, } } // FileUploaded converts the response to an event -func FileUploaded(r *provider.InitiateFileUploadResponse, req *provider.InitiateFileUploadRequest, executant *user.UserId) events.FileUploaded { +func FileUploaded(r *provider.InitiateFileUploadResponse, req *provider.InitiateFileUploadRequest, spaceOwner, executant *user.UserId) events.FileUploaded { return events.FileUploaded{ - Executant: executant, - Ref: req.Ref, + SpaceOwner: spaceOwner, + Executant: executant, + Ref: req.Ref, } } @@ -190,11 +193,12 @@ func FileDownloaded(r *provider.InitiateFileDownloadResponse, req *provider.Init } // ItemTrashed converts the response to an event -func ItemTrashed(r *provider.DeleteResponse, req *provider.DeleteRequest, executant *user.UserId) events.ItemTrashed { +func ItemTrashed(r *provider.DeleteResponse, req *provider.DeleteRequest, spaceOwner, executant *user.UserId) events.ItemTrashed { opaqueID := utils.ReadPlainFromOpaque(r.Opaque, "opaque_id") return events.ItemTrashed{ - Executant: executant, - Ref: req.Ref, + SpaceOwner: spaceOwner, + Executant: executant, + Ref: req.Ref, ID: &provider.ResourceId{ StorageId: req.Ref.GetResourceId().GetStorageId(), SpaceId: req.Ref.GetResourceId().GetSpaceId(), @@ -204,8 +208,9 @@ func ItemTrashed(r *provider.DeleteResponse, req *provider.DeleteRequest, execut } // ItemMoved converts the response to an event -func ItemMoved(r *provider.MoveResponse, req *provider.MoveRequest, executant *user.UserId) events.ItemMoved { +func ItemMoved(r *provider.MoveResponse, req *provider.MoveRequest, spaceOwner, executant *user.UserId) events.ItemMoved { return events.ItemMoved{ + SpaceOwner: spaceOwner, Executant: executant, Ref: req.Destination, OldReference: req.Source, @@ -221,12 +226,13 @@ func ItemPurged(r *provider.PurgeRecycleResponse, req *provider.PurgeRecycleRequ } // ItemRestored converts the response to an event -func ItemRestored(r *provider.RestoreRecycleItemResponse, req *provider.RestoreRecycleItemRequest, executant *user.UserId) events.ItemRestored { +func ItemRestored(r *provider.RestoreRecycleItemResponse, req *provider.RestoreRecycleItemRequest, spaceOwner, executant *user.UserId) events.ItemRestored { ref := req.Ref if req.RestoreRef != nil { ref = req.RestoreRef } return events.ItemRestored{ + SpaceOwner: spaceOwner, Executant: executant, Ref: ref, OldReference: req.Ref, @@ -235,11 +241,12 @@ func ItemRestored(r *provider.RestoreRecycleItemResponse, req *provider.RestoreR } // FileVersionRestored converts the response to an event -func FileVersionRestored(r *provider.RestoreFileVersionResponse, req *provider.RestoreFileVersionRequest, executant *user.UserId) events.FileVersionRestored { +func FileVersionRestored(r *provider.RestoreFileVersionResponse, req *provider.RestoreFileVersionRequest, spaceOwner, executant *user.UserId) events.FileVersionRestored { return events.FileVersionRestored{ - Executant: executant, - Ref: req.Ref, - Key: req.Key, + SpaceOwner: spaceOwner, + Executant: executant, + Ref: req.Ref, + Key: req.Key, } } diff --git a/internal/grpc/interceptors/eventsmiddleware/events.go b/internal/grpc/interceptors/eventsmiddleware/events.go index 928f742f04..9dd87ed305 100644 --- a/internal/grpc/interceptors/eventsmiddleware/events.go +++ b/internal/grpc/interceptors/eventsmiddleware/events.go @@ -40,6 +40,7 @@ import ( "github.com/cs3org/reva/v2/pkg/events" "github.com/cs3org/reva/v2/pkg/events/server" "github.com/cs3org/reva/v2/pkg/rgrpc" + "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" "github.com/go-micro/plugins/v4/events/natsjs" ) @@ -63,11 +64,22 @@ func NewUnary(m map[string]interface{}) (grpc.UnaryServerInterceptor, int, error } interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + // Register a channel in the context to receive the space owner id from the handler(s) further down the stack + var ownerID *user.UserId + sendOwnerChan := make(chan *user.UserId) + ctx = storagespace.ContextRegisterSendOwnerChan(ctx, sendOwnerChan) + res, err := handler(ctx, req) if err != nil { return res, err } + // Read the space owner id from the channel + select { + case ownerID = <-sendOwnerChan: + default: + } + var executantID *user.UserId u, ok := revactx.ContextGetUser(ctx) if ok { @@ -120,7 +132,7 @@ func NewUnary(m map[string]interface{}) (grpc.UnaryServerInterceptor, int, error } case *provider.CreateContainerResponse: if isSuccess(v) { - ev = ContainerCreated(v, req.(*provider.CreateContainerRequest), executantID) + ev = ContainerCreated(v, req.(*provider.CreateContainerRequest), ownerID, executantID) } case *provider.InitiateFileDownloadResponse: if isSuccess(v) { @@ -128,11 +140,11 @@ func NewUnary(m map[string]interface{}) (grpc.UnaryServerInterceptor, int, error } case *provider.DeleteResponse: if isSuccess(v) { - ev = ItemTrashed(v, req.(*provider.DeleteRequest), executantID) + ev = ItemTrashed(v, req.(*provider.DeleteRequest), ownerID, executantID) } case *provider.MoveResponse: if isSuccess(v) { - ev = ItemMoved(v, req.(*provider.MoveRequest), executantID) + ev = ItemMoved(v, req.(*provider.MoveRequest), ownerID, executantID) } case *provider.PurgeRecycleResponse: if isSuccess(v) { @@ -140,11 +152,11 @@ func NewUnary(m map[string]interface{}) (grpc.UnaryServerInterceptor, int, error } case *provider.RestoreRecycleItemResponse: if isSuccess(v) { - ev = ItemRestored(v, req.(*provider.RestoreRecycleItemRequest), executantID) + ev = ItemRestored(v, req.(*provider.RestoreRecycleItemRequest), ownerID, executantID) } case *provider.RestoreFileVersionResponse: if isSuccess(v) { - ev = FileVersionRestored(v, req.(*provider.RestoreFileVersionRequest), executantID) + ev = FileVersionRestored(v, req.(*provider.RestoreFileVersionRequest), ownerID, executantID) } case *provider.CreateStorageSpaceResponse: if isSuccess(v) && v.StorageSpace != nil { // TODO: Why are there CreateStorageSpaceResponses with nil StorageSpace? @@ -172,7 +184,7 @@ func NewUnary(m map[string]interface{}) (grpc.UnaryServerInterceptor, int, error } case *provider.TouchFileResponse: if isSuccess(v) { - ev = FileTouched(v, req.(*provider.TouchFileRequest), executantID) + ev = FileTouched(v, req.(*provider.TouchFileRequest), ownerID, executantID) } } diff --git a/pkg/events/files.go b/pkg/events/files.go index 051d6237cf..802072ee75 100644 --- a/pkg/events/files.go +++ b/pkg/events/files.go @@ -27,9 +27,10 @@ import ( // ContainerCreated is emitted when a directory has been created type ContainerCreated struct { - Executant *user.UserId - Ref *provider.Reference - Owner *user.UserId + SpaceOwner *user.UserId + Executant *user.UserId + Ref *provider.Reference + Owner *user.UserId } // Unmarshal to fulfill umarshaller interface @@ -41,9 +42,10 @@ func (ContainerCreated) Unmarshal(v []byte) (interface{}, error) { // FileUploaded is emitted when a file is uploaded type FileUploaded struct { - Executant *user.UserId - Ref *provider.Reference - Owner *user.UserId + SpaceOwner *user.UserId + Executant *user.UserId + Ref *provider.Reference + Owner *user.UserId } // Unmarshal to fulfill umarshaller interface @@ -55,8 +57,9 @@ func (FileUploaded) Unmarshal(v []byte) (interface{}, error) { // FileTouched is emitted when a file is uploaded type FileTouched struct { - Executant *user.UserId - Ref *provider.Reference + SpaceOwner *user.UserId + Executant *user.UserId + Ref *provider.Reference } // Unmarshal to fulfill umarshaller interface @@ -82,10 +85,11 @@ func (FileDownloaded) Unmarshal(v []byte) (interface{}, error) { // ItemTrashed is emitted when a file or folder is trashed type ItemTrashed struct { - Executant *user.UserId - ID *provider.ResourceId - Ref *provider.Reference - Owner *user.UserId + SpaceOwner *user.UserId + Executant *user.UserId + ID *provider.ResourceId + Ref *provider.Reference + Owner *user.UserId } // Unmarshal to fulfill umarshaller interface @@ -97,6 +101,7 @@ func (ItemTrashed) Unmarshal(v []byte) (interface{}, error) { // ItemMoved is emitted when a file or folder is moved type ItemMoved struct { + SpaceOwner *user.UserId Executant *user.UserId Ref *provider.Reference Owner *user.UserId @@ -127,6 +132,7 @@ func (ItemPurged) Unmarshal(v []byte) (interface{}, error) { // ItemRestored is emitted when a file or folder is restored from trashbin type ItemRestored struct { + SpaceOwner *user.UserId Executant *user.UserId ID *provider.ResourceId Ref *provider.Reference @@ -144,10 +150,11 @@ func (ItemRestored) Unmarshal(v []byte) (interface{}, error) { // FileVersionRestored is emitted when a file version is restored type FileVersionRestored struct { - Executant *user.UserId - Ref *provider.Reference - Owner *user.UserId - Key string + SpaceOwner *user.UserId + Executant *user.UserId + Ref *provider.Reference + Owner *user.UserId + Key string } // Unmarshal to fulfill umarshaller interface diff --git a/pkg/rhttp/datatx/datatx.go b/pkg/rhttp/datatx/datatx.go index abd1b2fdf7..e7c8bedcb5 100644 --- a/pkg/rhttp/datatx/datatx.go +++ b/pkg/rhttp/datatx/datatx.go @@ -36,15 +36,16 @@ type DataTX interface { } // EmitFileUploadedEvent is a helper function which publishes a FileUploaded event -func EmitFileUploadedEvent(owner *userv1beta1.UserId, ref *provider.Reference, publisher events.Publisher) error { +func EmitFileUploadedEvent(spaceOwnerOrManager, executant *userv1beta1.UserId, ref *provider.Reference, publisher events.Publisher) error { if ref == nil || publisher == nil { return nil } uploadedEv := events.FileUploaded{ - Owner: owner, - Executant: owner, - Ref: ref, + SpaceOwner: spaceOwnerOrManager, + Owner: spaceOwnerOrManager, + Executant: executant, + Ref: ref, } return events.Publish(publisher, uploadedEv) diff --git a/pkg/rhttp/datatx/manager/simple/simple.go b/pkg/rhttp/datatx/manager/simple/simple.go index ecb3ec23f0..13f2bfbe31 100644 --- a/pkg/rhttp/datatx/manager/simple/simple.go +++ b/pkg/rhttp/datatx/manager/simple/simple.go @@ -93,9 +93,9 @@ func (m *manager) Handler(fs storage.FS) (http.Handler, error) { defer r.Body.Close() ref := &provider.Reference{Path: fn} - info, err := fs.Upload(ctx, ref, r.Body, func(owner *userpb.UserId, ref *provider.Reference) { + info, err := fs.Upload(ctx, ref, r.Body, func(spaceOwner, owner *userpb.UserId, ref *provider.Reference) { datatx.InvalidateCache(owner, ref, m.statCache) - if err := datatx.EmitFileUploadedEvent(owner, ref, m.publisher); err != nil { + if err := datatx.EmitFileUploadedEvent(spaceOwner, owner, ref, m.publisher); err != nil { sublog.Error().Err(err).Msg("failed to publish FileUploaded event") } }) diff --git a/pkg/rhttp/datatx/manager/spaces/spaces.go b/pkg/rhttp/datatx/manager/spaces/spaces.go index 61802a44d6..b4ec4bddef 100644 --- a/pkg/rhttp/datatx/manager/spaces/spaces.go +++ b/pkg/rhttp/datatx/manager/spaces/spaces.go @@ -107,9 +107,9 @@ func (m *manager) Handler(fs storage.FS) (http.Handler, error) { Path: fn, } var info provider.ResourceInfo - info, err = fs.Upload(ctx, ref, r.Body, func(owner *userpb.UserId, ref *provider.Reference) { + info, err = fs.Upload(ctx, ref, r.Body, func(spaceOwner, owner *userpb.UserId, ref *provider.Reference) { datatx.InvalidateCache(owner, ref, m.statCache) - if err := datatx.EmitFileUploadedEvent(owner, ref, m.publisher); err != nil { + if err := datatx.EmitFileUploadedEvent(spaceOwner, owner, ref, m.publisher); err != nil { sublog.Error().Err(err).Msg("failed to publish FileUploaded event") } }) diff --git a/pkg/rhttp/datatx/manager/tus/tus.go b/pkg/rhttp/datatx/manager/tus/tus.go index 92904ac808..c8807d4ffc 100644 --- a/pkg/rhttp/datatx/manager/tus/tus.go +++ b/pkg/rhttp/datatx/manager/tus/tus.go @@ -110,6 +110,9 @@ func (m *manager) Handler(fs storage.FS) (http.Handler, error) { for { ev := <-handler.CompleteUploads info := ev.Upload + spaceOwner := &userv1beta1.UserId{ + OpaqueId: info.Storage["SpaceOwnerOrManager"], + } owner := &userv1beta1.UserId{ Idp: info.Storage["Idp"], OpaqueId: info.Storage["UserId"], @@ -124,7 +127,7 @@ func (m *manager) Handler(fs storage.FS) (http.Handler, error) { } datatx.InvalidateCache(owner, ref, m.statCache) if m.publisher != nil { - if err := datatx.EmitFileUploadedEvent(owner, ref, m.publisher); err != nil { + if err := datatx.EmitFileUploadedEvent(spaceOwner, owner, ref, m.publisher); err != nil { appctx.GetLogger(context.Background()).Error().Err(err).Msg("failed to publish FileUploaded event") } } diff --git a/pkg/storage/fs/owncloudsql/upload.go b/pkg/storage/fs/owncloudsql/upload.go index a77c2e0341..9177292f27 100644 --- a/pkg/storage/fs/owncloudsql/upload.go +++ b/pkg/storage/fs/owncloudsql/upload.go @@ -103,7 +103,10 @@ func (fs *owncloudsqlfs) Upload(ctx context.Context, ref *provider.Reference, r if !ok { return provider.ResourceInfo{}, errtypes.PreconditionFailed("error getting user from uploadinfo context") } - uff(owner.Id, uploadRef) + // spaces support in localfs needs to be revisited: + // * info.Storage["SpaceRoot"] is never set + // * there is no space owner or manager that could be passed to the UploadFinishedFunc + uff(owner.Id, owner.Id, uploadRef) } ri := provider.ResourceInfo{ diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index d1b0815001..e8f16a262c 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -31,7 +31,7 @@ import ( ) // UploadFinishedFunc is a callback function used in storage drivers to indicate that an upload has finished -type UploadFinishedFunc func(owner *userpb.UserId, ref *provider.Reference) +type UploadFinishedFunc func(spaceOwner, owner *userpb.UserId, ref *provider.Reference) // FS is the interface to implement access to the storage. type FS interface { diff --git a/pkg/storage/utils/decomposedfs/decomposedfs.go b/pkg/storage/utils/decomposedfs/decomposedfs.go index 6b7b913f76..dc0d389a16 100644 --- a/pkg/storage/utils/decomposedfs/decomposedfs.go +++ b/pkg/storage/utils/decomposedfs/decomposedfs.go @@ -49,6 +49,7 @@ import ( "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/xattrs" "github.com/cs3org/reva/v2/pkg/storage/utils/templates" + "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" "github.com/pkg/errors" "go.opentelemetry.io/otel/codes" @@ -307,6 +308,9 @@ func (fs *Decomposedfs) CreateDir(ctx context.Context, ref *provider.Reference) return errtypes.PermissionDenied(filepath.Join(n.ParentID, n.Name)) } + // Set space owner in context + storagespace.ContextSendSpaceOwnerID(ctx, n.SpaceOwnerOrManager(ctx)) + // check lock if err := n.CheckLock(ctx); err != nil { return err @@ -367,6 +371,9 @@ func (fs *Decomposedfs) TouchFile(ctx context.Context, ref *provider.Reference) return errtypes.PermissionDenied(filepath.Join(n.ParentID, n.Name)) } + // Set space owner in context + storagespace.ContextSendSpaceOwnerID(ctx, n.SpaceOwnerOrManager(ctx)) + // check lock if err := n.CheckLock(ctx); err != nil { return err @@ -504,6 +511,9 @@ func (fs *Decomposedfs) Move(ctx context.Context, oldRef, newRef *provider.Refer return errtypes.PermissionDenied(newNode.ID) } + // Set space owner in context + storagespace.ContextSendSpaceOwnerID(ctx, newNode.SpaceOwnerOrManager(ctx)) + // check lock on source if err := oldNode.CheckLock(ctx); err != nil { return err @@ -620,6 +630,9 @@ func (fs *Decomposedfs) Delete(ctx context.Context, ref *provider.Reference) (er return errtypes.PermissionDenied(filepath.Join(node.ParentID, node.Name)) } + // Set space owner in context + storagespace.ContextSendSpaceOwnerID(ctx, node.SpaceOwnerOrManager(ctx)) + if err := node.CheckLock(ctx); err != nil { return err } diff --git a/pkg/storage/utils/decomposedfs/metadata.go b/pkg/storage/utils/decomposedfs/metadata.go index 39721f4cdc..4d7357d168 100644 --- a/pkg/storage/utils/decomposedfs/metadata.go +++ b/pkg/storage/utils/decomposedfs/metadata.go @@ -30,6 +30,7 @@ import ( "github.com/cs3org/reva/v2/pkg/errtypes" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/xattrs" + "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" "github.com/pkg/errors" "github.com/pkg/xattr" @@ -59,6 +60,9 @@ func (fs *Decomposedfs) SetArbitraryMetadata(ctx context.Context, ref *provider. return errtypes.PermissionDenied(filepath.Join(n.ParentID, n.Name)) } + // Set space owner in context + storagespace.ContextSendSpaceOwnerID(ctx, n.SpaceOwnerOrManager(ctx)) + // check lock if err := n.CheckLock(ctx); err != nil { return err @@ -152,6 +156,9 @@ func (fs *Decomposedfs) UnsetArbitraryMetadata(ctx context.Context, ref *provide return errtypes.PermissionDenied(filepath.Join(n.ParentID, n.Name)) } + // Set space owner in context + storagespace.ContextSendSpaceOwnerID(ctx, n.SpaceOwnerOrManager(ctx)) + // check lock if err := n.CheckLock(ctx); err != nil { return err diff --git a/pkg/storage/utils/decomposedfs/node/node.go b/pkg/storage/utils/decomposedfs/node/node.go index c9190e7436..aaaa3998f6 100644 --- a/pkg/storage/utils/decomposedfs/node/node.go +++ b/pkg/storage/utils/decomposedfs/node/node.go @@ -181,6 +181,27 @@ func (n *Node) WriteOwner(owner *userpb.UserId) error { return nil } +// SpaceOwnerOrManager returns the space owner of the space. If no owner is set +// one of the space managers is returned instead. +func (n *Node) SpaceOwnerOrManager(ctx context.Context) *userpb.UserId { + if n.Owner() != nil { + return n.Owner() + } + + // We don't have an owner set. Find a manager instead. + grants, err := n.SpaceRoot.ListGrants(ctx) + if err != nil { + return nil + } + for _, grant := range grants { + if grant.Permissions.Stat && grant.Permissions.ListContainer && grant.Permissions.InitiateFileDownload { + return grant.GetGrantee().GetUserId() + } + } + + return nil +} + // ReadNode creates a new instance from an id and checks if it exists func ReadNode(ctx context.Context, lu PathLookup, spaceID, nodeID string, canListDisabledSpace bool) (n *Node, err error) { diff --git a/pkg/storage/utils/decomposedfs/node/node_test.go b/pkg/storage/utils/decomposedfs/node/node_test.go index d6ba130148..db9aeee035 100644 --- a/pkg/storage/utils/decomposedfs/node/node_test.go +++ b/pkg/storage/utils/decomposedfs/node/node_test.go @@ -227,4 +227,19 @@ var _ = Describe("Node", func() { }) }) }) + + Describe("SpaceOwnerOrManager", func() { + It("returns the space owner", func() { + n, err := env.Lookup.NodeFromResource(env.Ctx, &provider.Reference{ + ResourceId: env.SpaceRootRes, + Path: "dir1/file1", + }) + Expect(err).ToNot(HaveOccurred()) + + o := n.SpaceOwnerOrManager(env.Ctx) + Expect(err).ToNot(HaveOccurred()) + Expect(o).To(Equal(env.Owner.Id)) + }) + + }) }) diff --git a/pkg/storage/utils/decomposedfs/recycle.go b/pkg/storage/utils/decomposedfs/recycle.go index e01fd63210..9ce9007a80 100644 --- a/pkg/storage/utils/decomposedfs/recycle.go +++ b/pkg/storage/utils/decomposedfs/recycle.go @@ -33,6 +33,7 @@ import ( "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/xattrs" + "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/pkg/errors" "github.com/pkg/xattr" ) @@ -273,6 +274,9 @@ func (fs *Decomposedfs) RestoreRecycleItem(ctx context.Context, ref *provider.Re return errtypes.PermissionDenied(key) } + // Set space owner in context + storagespace.ContextSendSpaceOwnerID(ctx, rn.SpaceOwnerOrManager(ctx)) + // check we can write to the parent of the restore reference ps, err := fs.p.AssemblePermissions(ctx, parent) if err != nil { diff --git a/pkg/storage/utils/decomposedfs/revisions.go b/pkg/storage/utils/decomposedfs/revisions.go index e2c1479a11..c742c4d8e4 100644 --- a/pkg/storage/utils/decomposedfs/revisions.go +++ b/pkg/storage/utils/decomposedfs/revisions.go @@ -31,6 +31,7 @@ import ( "github.com/cs3org/reva/v2/pkg/appctx" "github.com/cs3org/reva/v2/pkg/errtypes" "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" + "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/pkg/errors" ) @@ -173,6 +174,9 @@ func (fs *Decomposedfs) RestoreRevision(ctx context.Context, ref *provider.Refer return errtypes.PermissionDenied(filepath.Join(n.ParentID, n.Name)) } + // Set space owner in context + storagespace.ContextSendSpaceOwnerID(ctx, n.SpaceOwnerOrManager(ctx)) + // check lock if err := n.CheckLock(ctx); err != nil { return err diff --git a/pkg/storage/utils/decomposedfs/upload.go b/pkg/storage/utils/decomposedfs/upload.go index 84bf3a58cf..489920d2e4 100644 --- a/pkg/storage/utils/decomposedfs/upload.go +++ b/pkg/storage/utils/decomposedfs/upload.go @@ -114,7 +114,10 @@ func (fs *Decomposedfs) Upload(ctx context.Context, ref *provider.Reference, r i if !ok { return provider.ResourceInfo{}, errtypes.PreconditionFailed("error getting user from uploadinfo context") } - uff(owner.Id, uploadRef) + spaceOwner := &userpb.UserId{ + OpaqueId: info.Storage["SpaceOwnerOrManager"], + } + uff(spaceOwner, owner.Id, uploadRef) } ri := provider.ResourceInfo{ @@ -162,7 +165,8 @@ func (fs *Decomposedfs) InitiateUpload(ctx context.Context, ref *provider.Refere }, Size: uploadLength, Storage: map[string]string{ - "SpaceRoot": n.SpaceRoot.ID, + "SpaceRoot": n.SpaceRoot.ID, + "SpaceOwnerOrManager": n.SpaceOwnerOrManager(ctx).GetOpaqueId(), }, } @@ -314,10 +318,11 @@ func (fs *Decomposedfs) NewUpload(ctx context.Context, info tusd.FileInfo) (uplo "Type": "OCISStore", "BinPath": binPath, - "NodeId": n.ID, - "NodeParentId": n.ParentID, - "NodeName": n.Name, - "SpaceRoot": spaceRoot, + "NodeId": n.ID, + "NodeParentId": n.ParentID, + "NodeName": n.Name, + "SpaceRoot": spaceRoot, + "SpaceOwnerOrManager": info.Storage["SpaceOwnerOrManager"], "Idp": usr.Id.Idp, "UserId": usr.Id.OpaqueId, diff --git a/pkg/storage/utils/localfs/upload.go b/pkg/storage/utils/localfs/upload.go index 2c46ea9ba3..f0b627d8af 100644 --- a/pkg/storage/utils/localfs/upload.go +++ b/pkg/storage/utils/localfs/upload.go @@ -94,7 +94,10 @@ func (fs *localfs) Upload(ctx context.Context, ref *provider.Reference, r io.Rea if !ok { return provider.ResourceInfo{}, errtypes.PreconditionFailed("error getting user from uploadinfo context") } - uff(owner.Id, uploadRef) + // spaces support in localfs needs to be revisited: + // * info.Storage["SpaceRoot"] is never set + // * there is no space owner or manager that could be passed to the UploadFinishedFunc + uff(owner.Id, owner.Id, uploadRef) } // return id, etag and mtime diff --git a/pkg/storagespace/context.go b/pkg/storagespace/context.go new file mode 100644 index 0000000000..e919e3dfc6 --- /dev/null +++ b/pkg/storagespace/context.go @@ -0,0 +1,47 @@ +// 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 storagespace + +import ( + "context" + + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" +) + +type key int + +const ( + sendOwnerChanKey key = iota +) + +// ContextSendSpaceOwnerID stores the space owner in the context. +func ContextSendSpaceOwnerID(ctx context.Context, id *userpb.UserId) { + ch, ok := ctx.Value(sendOwnerChanKey).(chan<- *userpb.UserId) + if ok { + go func() { + ch <- id + close(ch) + }() + } +} + +// ContextRegisterSendOwnerChan registers a channel to send the current space owner +func ContextRegisterSendOwnerChan(ctx context.Context, ch chan<- *userpb.UserId) context.Context { + return context.WithValue(ctx, sendOwnerChanKey, ch) +}