mirror of
https://github.com/drakkan/sftpgo.git
synced 2026-10-21 07:57:48 +00:00
common: check the target write permission and quota on the server-side copy
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
@@ -675,7 +675,9 @@ func (c *BaseConnection) checkCopyPermissions(virtualSourcePath, virtualTargetPa
|
||||
return c.checkCopyTargetPattern(virtualTargetPath)
|
||||
}
|
||||
|
||||
func (c *BaseConnection) copyFile(virtualSourcePath, virtualTargetPath string, srcInfo os.FileInfo) error {
|
||||
// copyFile copies a regular file. dstInfo describes the target as the caller
|
||||
// resolved it, it is nil if the target does not exist.
|
||||
func (c *BaseConnection) copyFile(virtualSourcePath, virtualTargetPath string, srcInfo, dstInfo os.FileInfo) error {
|
||||
if err := c.checkCopyPermissions(virtualSourcePath, virtualTargetPath); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -685,19 +687,32 @@ func (c *BaseConnection) copyFile(virtualSourcePath, virtualTargetPath string, s
|
||||
return err
|
||||
}
|
||||
if copier, ok := fs.(vfs.FsFileCopier); ok {
|
||||
_, fsSourcePath, err := c.GetFsAndResolvedPath(virtualSourcePath)
|
||||
if err != nil {
|
||||
// A target that exists but is not a regular file is left to the
|
||||
// streaming path, which reports the appropriate error.
|
||||
if dstInfo == nil || dstInfo.Mode().IsRegular() {
|
||||
numFiles := 1
|
||||
var truncatedSize int64
|
||||
if dstInfo != nil {
|
||||
numFiles = 0
|
||||
truncatedSize = dstInfo.Size()
|
||||
}
|
||||
if err := checkWriterPermsAndQuota(c, virtualTargetPath, numFiles, srcInfo.Size(), truncatedSize); err != nil {
|
||||
return err
|
||||
}
|
||||
_, fsSourcePath, err := c.GetFsAndResolvedPath(virtualSourcePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
startTime := time.Now()
|
||||
numFiles, sizeDiff, err := copier.CopyFile(fsSourcePath, fsTargetPath, srcInfo)
|
||||
elapsed := time.Since(startTime).Nanoseconds() / 1000000
|
||||
updateUserQuotaAfterFileWrite(c, virtualTargetPath, numFiles, sizeDiff)
|
||||
logger.CommandLog(copyLogSender, fsSourcePath, fsTargetPath, virtualSourcePath, virtualTargetPath,
|
||||
c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "", srcInfo.Size(),
|
||||
c.localAddr, c.remoteAddr, elapsed)
|
||||
ExecuteActionNotification(c, operationCopy, fsSourcePath, virtualSourcePath, fsTargetPath, virtualTargetPath, "", srcInfo.Size(), err, elapsed, nil) //nolint:errcheck
|
||||
return err
|
||||
}
|
||||
startTime := time.Now()
|
||||
numFiles, sizeDiff, err := copier.CopyFile(fsSourcePath, fsTargetPath, srcInfo)
|
||||
elapsed := time.Since(startTime).Nanoseconds() / 1000000
|
||||
updateUserQuotaAfterFileWrite(c, virtualTargetPath, numFiles, sizeDiff)
|
||||
logger.CommandLog(copyLogSender, fsSourcePath, fsTargetPath, virtualSourcePath, virtualTargetPath,
|
||||
c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "", srcInfo.Size(),
|
||||
c.localAddr, c.remoteAddr, elapsed)
|
||||
ExecuteActionNotification(c, operationCopy, fsSourcePath, virtualSourcePath, fsTargetPath, virtualTargetPath, "", srcInfo.Size(), err, elapsed, nil) //nolint:errcheck
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -720,7 +735,7 @@ func (c *BaseConnection) copyFile(virtualSourcePath, virtualTargetPath string, s
|
||||
err, operationCopy, startTime)
|
||||
}
|
||||
|
||||
func (c *BaseConnection) doRecursiveCopy(virtualSourcePath, virtualTargetPath string, srcInfo os.FileInfo,
|
||||
func (c *BaseConnection) doRecursiveCopy(virtualSourcePath, virtualTargetPath string, srcInfo, dstInfo os.FileInfo,
|
||||
createTargetDir bool, recursion int,
|
||||
) error {
|
||||
if srcInfo.IsDir() {
|
||||
@@ -759,7 +774,7 @@ func (c *BaseConnection) doRecursiveCopy(virtualSourcePath, virtualTargetPath st
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.copyFile(virtualSourcePath, virtualTargetPath, srcInfo)
|
||||
return c.copyFile(virtualSourcePath, virtualTargetPath, srcInfo, dstInfo)
|
||||
}
|
||||
|
||||
func (c *BaseConnection) recursiveCopyEntries(virtualSourcePath, virtualTargetPath string, entries []os.FileInfo, recursion int) error {
|
||||
@@ -779,7 +794,7 @@ func (c *BaseConnection) recursiveCopyEntries(virtualSourcePath, virtualTargetPa
|
||||
if err := c.checkCopy(info, targetInfo, sourcePath, targetPath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.doRecursiveCopy(sourcePath, targetPath, info, true, recursion); err != nil {
|
||||
if err := c.doRecursiveCopy(sourcePath, targetPath, info, targetInfo, true, recursion); err != nil {
|
||||
if c.IsNotExistError(err) {
|
||||
c.Log(logger.LevelInfo, "skipping copy for source path %q: %v", sourcePath, err)
|
||||
continue
|
||||
@@ -853,7 +868,7 @@ func (c *BaseConnection) Copy(virtualSourcePath, virtualTargetPath string) error
|
||||
defer close(done)
|
||||
go keepConnectionAlive(c, done, 2*time.Minute)
|
||||
|
||||
return c.doRecursiveCopy(virtualSourcePath, destPath, srcInfo, createTargetDir, 0)
|
||||
return c.doRecursiveCopy(virtualSourcePath, destPath, srcInfo, dstInfo, createTargetDir, 0)
|
||||
}
|
||||
|
||||
// Rename renames (moves) virtualSourcePath to virtualTargetPath
|
||||
|
||||
@@ -680,7 +680,7 @@ func TestErrorResolvePath(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
err = conn.doRecursiveRemove(nil, "/fspath", "/vpath", vfs.NewFileInfo("vpath", true, 0, time.Now(), false), 2000)
|
||||
assert.Error(t, err, util.ErrRecursionTooDeep)
|
||||
err = conn.doRecursiveCopy("/src", "/dst", vfs.NewFileInfo("src", true, 0, time.Now(), false), false, 2000)
|
||||
err = conn.doRecursiveCopy("/src", "/dst", vfs.NewFileInfo("src", true, 0, time.Now(), false), nil, false, 2000)
|
||||
assert.Error(t, err, util.ErrRecursionTooDeep)
|
||||
err = conn.checkCopy(vfs.NewFileInfo("name", true, 0, time.Unix(0, 0), false), nil, "/source", "/target")
|
||||
assert.Error(t, err)
|
||||
@@ -1477,6 +1477,42 @@ func TestHiddenVirtualFolderStat(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestServerSideCopyWriteChecks(t *testing.T) {
|
||||
newConn := func(perms []string) *BaseConnection {
|
||||
user := dataprovider.User{
|
||||
BaseUser: sdk.BaseUser{
|
||||
Username: userTestUsername,
|
||||
HomeDir: filepath.Clean(os.TempDir()),
|
||||
Permissions: map[string][]string{"/": perms},
|
||||
},
|
||||
FsConfig: vfs.Filesystem{
|
||||
Provider: sdk.S3FilesystemProvider,
|
||||
S3Config: vfs.S3FsConfig{
|
||||
BaseS3FsConfig: sdk.BaseS3FsConfig{
|
||||
Bucket: "buck",
|
||||
Region: "us-east-1",
|
||||
AccessKey: "key",
|
||||
},
|
||||
AccessSecret: kms.NewPlainSecret("s3secret"),
|
||||
},
|
||||
},
|
||||
}
|
||||
return NewBaseConnection(xid.New().String(), ProtocolHTTP, "", "", user)
|
||||
}
|
||||
srcInfo := vfs.NewFileInfo("a.txt", false, 100, time.Now(), false)
|
||||
dstInfo := vfs.NewFileInfo("b.txt", false, 10, time.Now(), false)
|
||||
|
||||
// the target exists: the overwrite permission is required, upload is not enough
|
||||
conn := newConn([]string{dataprovider.PermListItems, dataprovider.PermDownload,
|
||||
dataprovider.PermUpload, dataprovider.PermCopy})
|
||||
assert.ErrorIs(t, conn.copyFile("/a.txt", "/b.txt", srcInfo, dstInfo), os.ErrPermission)
|
||||
|
||||
// the target does not exist: the upload permission is required, overwrite is not enough
|
||||
conn = newConn([]string{dataprovider.PermListItems, dataprovider.PermDownload,
|
||||
dataprovider.PermOverwrite, dataprovider.PermCopy})
|
||||
assert.ErrorIs(t, conn.copyFile("/a.txt", "/b.txt", srcInfo, nil), os.ErrPermission)
|
||||
}
|
||||
|
||||
func TestRecursiveRenameHiddenEntry(t *testing.T) {
|
||||
homeDir := filepath.Join(os.TempDir(), "renamewalk")
|
||||
err := os.RemoveAll(homeDir)
|
||||
|
||||
Reference in New Issue
Block a user