diff --git a/changelog/unreleased/enhancement-add-touch-file.md b/changelog/unreleased/enhancement-add-touch-file.md new file mode 100644 index 0000000000..8e9c04e0b9 --- /dev/null +++ b/changelog/unreleased/enhancement-add-touch-file.md @@ -0,0 +1,6 @@ +Enhancement: Implement TouchFile from the CS3apis + +We've updated the CS3apis and implemented the TouchFile method. + +https://github.com/cs3org/reva/pull/2369 +https://github.com/cs3org/cs3apis/pull/154 diff --git a/go.mod b/go.mod index 168ad5d487..b5b9d08395 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,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-20211104090126-8e972dca8304 + github.com/cs3org/go-cs3apis v0.0.0-20211213090556-12c0d565f51d github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 github.com/eventials/go-tus v0.0.0-20200718001131-45c7ec8f5d59 github.com/gdexlab/go-render v1.0.1 diff --git a/go.sum b/go.sum index 11ef1e3104..c937822ad9 100644 --- a/go.sum +++ b/go.sum @@ -111,8 +111,12 @@ 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-20210325133324-32b03d75a535 h1:555D8A3ddKqb4OyK9v5mdphw2zDLWKGXOkcnf1RQwTA= +github.com/cs3org/go-cs3apis v0.0.0-20210325133324-32b03d75a535/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= github.com/cs3org/go-cs3apis v0.0.0-20211104090126-8e972dca8304 h1:e/nIPR518vyvrulo9goAZTtYD6gFfu/2/9MDe6mTGcw= github.com/cs3org/go-cs3apis v0.0.0-20211104090126-8e972dca8304/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= +github.com/cs3org/go-cs3apis v0.0.0-20211213090556-12c0d565f51d h1:gnb2ciU4N+RwUug/nwe54wenWi7vSp5bAAjXINlgHZ8= +github.com/cs3org/go-cs3apis v0.0.0-20211213090556-12c0d565f51d/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= diff --git a/internal/grpc/interceptors/auth/scope.go b/internal/grpc/interceptors/auth/scope.go index 8c9a907ce3..8bad148478 100644 --- a/internal/grpc/interceptors/auth/scope.go +++ b/internal/grpc/interceptors/auth/scope.go @@ -241,6 +241,8 @@ func extractRef(req interface{}, hasEditorRole bool) (*provider.Reference, bool) // Write Requests case *provider.CreateContainerRequest: return v.GetRef(), true + case *provider.TouchFileRequest: + return v.GetRef(), true case *provider.DeleteRequest: return v.GetRef(), true case *provider.MoveRequest: diff --git a/internal/grpc/interceptors/readonly/readonly.go b/internal/grpc/interceptors/readonly/readonly.go index f82a7ab46b..d1d8801c2d 100644 --- a/internal/grpc/interceptors/readonly/readonly.go +++ b/internal/grpc/interceptors/readonly/readonly.go @@ -99,7 +99,11 @@ func NewUnary(map[string]interface{}) (grpc.UnaryServerInterceptor, int, error) }, nil case *provider.CreateContainerRequest: return &provider.CreateContainerResponse{ - Status: rstatus.NewPermissionDenied(ctx, nil, "permission denied: tried to create resoure on readonly storage"), + Status: rstatus.NewPermissionDenied(ctx, nil, "permission denied: tried to create resource on read-only storage"), + }, nil + case *provider.TouchFileRequest: + return &provider.TouchFileResponse{ + Status: rstatus.NewPermissionDenied(ctx, nil, "permission denied: tried to create resource on read-only storage"), }, nil case *provider.CreateHomeRequest: return &provider.CreateHomeResponse{ diff --git a/internal/grpc/services/gateway/storageprovider.go b/internal/grpc/services/gateway/storageprovider.go index 66599a324e..cdd622e9c7 100644 --- a/internal/grpc/services/gateway/storageprovider.go +++ b/internal/grpc/services/gateway/storageprovider.go @@ -851,6 +851,25 @@ func (s *svc) createContainer(ctx context.Context, req *provider.CreateContainer return res, nil } +func (s *svc) TouchFile(ctx context.Context, req *provider.TouchFileRequest) (*provider.TouchFileResponse, error) { + c, err := s.find(ctx, req.Ref) + if err != nil { + return &provider.TouchFileResponse{ + Status: status.NewStatusFromErrType(ctx, "TouchFile ref="+req.Ref.String(), err), + }, nil + } + + res, err := c.TouchFile(ctx, req) + if 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 +} + // check if the path contains the prefix of the shared folder func (s *svc) inSharedFolder(ctx context.Context, p string) bool { sharedFolder := s.getSharedFolder(ctx) diff --git a/internal/grpc/services/publicstorageprovider/publicstorageprovider.go b/internal/grpc/services/publicstorageprovider/publicstorageprovider.go index c6ec4e983e..80b64f4e99 100644 --- a/internal/grpc/services/publicstorageprovider/publicstorageprovider.go +++ b/internal/grpc/services/publicstorageprovider/publicstorageprovider.go @@ -363,6 +363,19 @@ func (s *service) CreateContainer(ctx context.Context, req *provider.CreateConta return res, nil } +func (s *service) TouchFile(ctx context.Context, req *provider.TouchFileRequest) (*provider.TouchFileResponse, error) { + ref, _, _, st, err := s.translatePublicRefToCS3Ref(ctx, req.Ref) + switch { + case err != nil: + return nil, err + case st != nil: + return &provider.TouchFileResponse{ + Status: st, + }, nil + } + return s.gateway.TouchFile(ctx, &provider.TouchFileRequest{Opaque: req.Opaque, Ref: ref}) +} + func (s *service) Delete(ctx context.Context, req *provider.DeleteRequest) (*provider.DeleteResponse, error) { ctx, span := rtrace.Provider.Tracer("publicstorageprovider").Start(ctx, "Delete") defer span.End() diff --git a/internal/grpc/services/storageprovider/storageprovider.go b/internal/grpc/services/storageprovider/storageprovider.go index aac7f68453..a0d659005a 100644 --- a/internal/grpc/services/storageprovider/storageprovider.go +++ b/internal/grpc/services/storageprovider/storageprovider.go @@ -544,6 +544,36 @@ func (s *service) CreateContainer(ctx context.Context, req *provider.CreateConta return res, nil } +func (s *service) TouchFile(ctx context.Context, req *provider.TouchFileRequest) (*provider.TouchFileResponse, error) { + newRef, err := s.unwrap(ctx, req.Ref) + if err != nil { + return &provider.TouchFileResponse{ + Status: status.NewInternal(ctx, err, "error unwrapping path"), + }, nil + } + if err := s.storage.TouchFile(ctx, newRef); 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, err, "error touching file: "+req.Ref.String()) + } + return &provider.TouchFileResponse{ + Status: st, + }, nil + } + + res := &provider.TouchFileResponse{ + Status: status.NewOK(ctx), + } + return res, nil +} + func (s *service) Delete(ctx context.Context, req *provider.DeleteRequest) (*provider.DeleteResponse, error) { newRef, err := s.unwrap(ctx, req.Ref) if err != nil { diff --git a/pkg/auth/scope/publicshare.go b/pkg/auth/scope/publicshare.go index b7f7d078b6..7de007ca87 100644 --- a/pkg/auth/scope/publicshare.go +++ b/pkg/auth/scope/publicshare.go @@ -57,6 +57,8 @@ func publicshareScope(ctx context.Context, scope *authpb.Scope, resource interfa // need to return appropriate status codes in the ocs/ocdav layers. case *provider.CreateContainerRequest: return hasRoleEditor(*scope) && checkStorageRef(ctx, &share, v.GetRef()), nil + case *provider.TouchFileRequest: + return hasRoleEditor(*scope) && checkStorageRef(ctx, &share, v.GetRef()), nil case *provider.DeleteRequest: return hasRoleEditor(*scope) && checkStorageRef(ctx, &share, v.GetRef()), nil case *provider.MoveRequest: diff --git a/pkg/auth/scope/resourceinfo.go b/pkg/auth/scope/resourceinfo.go index f5afe94cfe..1a6c41a40f 100644 --- a/pkg/auth/scope/resourceinfo.go +++ b/pkg/auth/scope/resourceinfo.go @@ -55,6 +55,8 @@ func resourceinfoScope(_ context.Context, scope *authpb.Scope, resource interfac // need to return appropriate status codes in the ocs/ocdav layers. case *provider.CreateContainerRequest: return hasRoleEditor(*scope) && checkResourceInfo(&r, v.GetRef()), nil + case *provider.TouchFileRequest: + return hasRoleEditor(*scope) && checkResourceInfo(&r, v.GetRef()), nil case *provider.DeleteRequest: return hasRoleEditor(*scope) && checkResourceInfo(&r, v.GetRef()), nil case *provider.MoveRequest: diff --git a/pkg/auth/scope/share.go b/pkg/auth/scope/share.go index 1e29fd44e1..48683dea17 100644 --- a/pkg/auth/scope/share.go +++ b/pkg/auth/scope/share.go @@ -56,6 +56,8 @@ func shareScope(_ context.Context, scope *authpb.Scope, resource interface{}, lo // need to return appropriate status codes in the ocs/ocdav layers. case *provider.CreateContainerRequest: return checkShareStorageRef(&share, v.GetRef()), nil + case *provider.TouchFileRequest: + return checkShareStorageRef(&share, v.GetRef()), nil case *provider.DeleteRequest: return checkShareStorageRef(&share, v.GetRef()), nil case *provider.MoveRequest: diff --git a/pkg/storage/fs/nextcloud/nextcloud.go b/pkg/storage/fs/nextcloud/nextcloud.go index 7ee908beda..567a880863 100644 --- a/pkg/storage/fs/nextcloud/nextcloud.go +++ b/pkg/storage/fs/nextcloud/nextcloud.go @@ -253,6 +253,11 @@ func (nc *StorageDriver) CreateDir(ctx context.Context, ref *provider.Reference) return err } +// TouchFile as defined in the storage.FS interface +func (nc *StorageDriver) TouchFile(ctx context.Context, ref *provider.Reference) error { + return fmt.Errorf("unimplemented: TouchFile") +} + // Delete as defined in the storage.FS interface func (nc *StorageDriver) Delete(ctx context.Context, ref *provider.Reference) error { bodyStr, err := json.Marshal(ref) diff --git a/pkg/storage/fs/owncloud/owncloud.go b/pkg/storage/fs/owncloud/owncloud.go index 51a1299ec3..51a5ef8b05 100644 --- a/pkg/storage/fs/owncloud/owncloud.go +++ b/pkg/storage/fs/owncloud/owncloud.go @@ -1186,6 +1186,11 @@ func (fs *ocfs) CreateDir(ctx context.Context, ref *provider.Reference) (err err 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) } diff --git a/pkg/storage/fs/owncloudsql/owncloudsql.go b/pkg/storage/fs/owncloudsql/owncloudsql.go index 43db8f3b89..05b929330f 100644 --- a/pkg/storage/fs/owncloudsql/owncloudsql.go +++ b/pkg/storage/fs/owncloudsql/owncloudsql.go @@ -766,6 +766,11 @@ func (fs *owncloudsqlfs) CreateDir(ctx context.Context, ref *provider.Reference) return fs.propagate(ctx, filepath.Dir(ip)) } +// TouchFile as defined in the storage.FS interface +func (fs *owncloudsqlfs) TouchFile(ctx context.Context, ref *provider.Reference) error { + return fmt.Errorf("unimplemented: TouchFile") +} + func (fs *owncloudsqlfs) CreateReference(ctx context.Context, sp string, targetURI *url.URL) error { return errtypes.NotSupported("owncloudsql: operation not supported") } diff --git a/pkg/storage/fs/s3/s3.go b/pkg/storage/fs/s3/s3.go index 39d9fb7d6a..330706aae0 100644 --- a/pkg/storage/fs/s3/s3.go +++ b/pkg/storage/fs/s3/s3.go @@ -326,6 +326,11 @@ func (fs *s3FS) CreateDir(ctx context.Context, ref *provider.Reference) error { return nil } +// TouchFile as defined in the storage.FS interface +func (fs *s3FS) TouchFile(ctx context.Context, ref *provider.Reference) error { + return fmt.Errorf("unimplemented: TouchFile") +} + func (fs *s3FS) Delete(ctx context.Context, ref *provider.Reference) error { log := appctx.GetLogger(ctx) diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index f43a938152..8b61b6f616 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -32,6 +32,7 @@ type FS interface { GetHome(ctx context.Context) (string, error) CreateHome(ctx context.Context) error CreateDir(ctx context.Context, ref *provider.Reference) error + TouchFile(ctx context.Context, ref *provider.Reference) error Delete(ctx context.Context, ref *provider.Reference) error Move(ctx context.Context, oldRef, newRef *provider.Reference) error GetMD(ctx context.Context, ref *provider.Reference, mdKeys []string) (*provider.ResourceInfo, error) diff --git a/pkg/storage/utils/decomposedfs/decomposedfs.go b/pkg/storage/utils/decomposedfs/decomposedfs.go index 4ebb5c3cb2..6b968febf2 100644 --- a/pkg/storage/utils/decomposedfs/decomposedfs.go +++ b/pkg/storage/utils/decomposedfs/decomposedfs.go @@ -23,6 +23,7 @@ package decomposedfs import ( "context" + "fmt" "io" "net/url" "os" @@ -320,6 +321,11 @@ func (fs *Decomposedfs) CreateDir(ctx context.Context, ref *provider.Reference) return } +// TouchFile as defined in the storage.FS interface +func (fs *Decomposedfs) TouchFile(ctx context.Context, ref *provider.Reference) error { + return fmt.Errorf("unimplemented: TouchFile") +} + // 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. diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index 3d045dd553..ce62dde0c5 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -1214,6 +1214,29 @@ func (fs *eosfs) CreateDir(ctx context.Context, ref *provider.Reference) error { return fs.c.CreateDir(ctx, auth, fn) } +// TouchFile as defined in the storage.FS interface +func (fs *eosfs) TouchFile(ctx context.Context, ref *provider.Reference) error { + log := appctx.GetLogger(ctx) + u, err := getUser(ctx) + if err != nil { + return errors.Wrap(err, "eosfs: no user in ctx") + } + p, err := fs.resolve(ctx, ref) + if err != nil { + return nil + } + + auth, err := fs.getUserAuth(ctx, u, p) + if err != nil { + return err + } + + log.Info().Msgf("eosfs: touch file: path=%s", p) + + fn := fs.wrap(ctx, p) + return fs.c.Touch(ctx, auth, fn) +} + func (fs *eosfs) CreateReference(ctx context.Context, p string, targetURI *url.URL) error { // TODO(labkode): for the time being we only allow creating references // in the virtual share folder to not pollute the nominal user tree. diff --git a/pkg/storage/utils/localfs/localfs.go b/pkg/storage/utils/localfs/localfs.go index 9f807d9ad2..4dc7c37e78 100644 --- a/pkg/storage/utils/localfs/localfs.go +++ b/pkg/storage/utils/localfs/localfs.go @@ -782,6 +782,11 @@ func (fs *localfs) CreateDir(ctx context.Context, ref *provider.Reference) error return fs.propagate(ctx, path.Dir(fn)) } +// TouchFile as defined in the storage.FS interface +func (fs *localfs) TouchFile(ctx context.Context, ref *provider.Reference) error { + return fmt.Errorf("unimplemented: TouchFile") +} + func (fs *localfs) Delete(ctx context.Context, ref *provider.Reference) error { fn, err := fs.resolve(ctx, ref) if err != nil {