mirror of
https://github.com/drakkan/sftpgo.git
synced 2025-11-23 22:04:50 +02:00
add a new permission for overwriting existing files
The upload permission is required to allow file overwrite
This commit is contained in:
@@ -282,6 +282,7 @@ For each account the following properties can be configured:
|
|||||||
- `list` list items is allowed
|
- `list` list items is allowed
|
||||||
- `download` download files is allowed
|
- `download` download files is allowed
|
||||||
- `upload` upload files is allowed
|
- `upload` upload files is allowed
|
||||||
|
- `overwrite` overwrite an existing file, while uploading, is allowed. `upload` permission is required to allow file overwrite
|
||||||
- `delete` delete files or directories is allowed
|
- `delete` delete files or directories is allowed
|
||||||
- `rename` rename files or directories is allowed
|
- `rename` rename files or directories is allowed
|
||||||
- `create_dirs` create directories is allowed
|
- `create_dirs` create directories is allowed
|
||||||
@@ -400,6 +401,7 @@ The logs can be divided into the following categories:
|
|||||||
- [viper](https://github.com/spf13/viper)
|
- [viper](https://github.com/spf13/viper)
|
||||||
- [cobra](https://github.com/spf13/cobra)
|
- [cobra](https://github.com/spf13/cobra)
|
||||||
- [xid](https://github.com/rs/xid)
|
- [xid](https://github.com/rs/xid)
|
||||||
|
- [nathanaelle/password](https://github.com/nathanaelle/password)
|
||||||
|
|
||||||
Some code was initially taken from [Pterodactyl sftp server](https://github.com/pterodactyl/sftp-server)
|
Some code was initially taken from [Pterodactyl sftp server](https://github.com/pterodactyl/sftp-server)
|
||||||
|
|
||||||
|
|||||||
@@ -511,6 +511,7 @@ components:
|
|||||||
- list
|
- list
|
||||||
- download
|
- download
|
||||||
- upload
|
- upload
|
||||||
|
- overwrite
|
||||||
- delete
|
- delete
|
||||||
- rename
|
- rename
|
||||||
- create_dirs
|
- create_dirs
|
||||||
@@ -521,6 +522,7 @@ components:
|
|||||||
* `list` - list items is allowed
|
* `list` - list items is allowed
|
||||||
* `download` - download files is allowed
|
* `download` - download files is allowed
|
||||||
* `upload` - upload files is allowed
|
* `upload` - upload files is allowed
|
||||||
|
* `overwrite` - overwrite an existing file, while uploading, is allowed. upload permission is required to allow file overwrite
|
||||||
* `delete` - delete files or directories is allowed
|
* `delete` - delete files or directories is allowed
|
||||||
* `rename` - rename files or directories is allowed
|
* `rename` - rename files or directories is allowed
|
||||||
* `create_dirs` - create directories is allowed
|
* `create_dirs` - create directories is allowed
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ var (
|
|||||||
provider Provider
|
provider Provider
|
||||||
sqlPlaceholders []string
|
sqlPlaceholders []string
|
||||||
validPerms = []string{PermAny, PermListItems, PermDownload, PermUpload, PermDelete, PermRename,
|
validPerms = []string{PermAny, PermListItems, PermDownload, PermUpload, PermDelete, PermRename,
|
||||||
PermCreateDirs, PermCreateSymlinks}
|
PermCreateDirs, PermCreateSymlinks, PermOverwrite}
|
||||||
hashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix, pbkdf2SHA1Prefix, pbkdf2SHA256Prefix,
|
hashPwdPrefixes = []string{argonPwdPrefix, bcryptPwdPrefix, pbkdf2SHA1Prefix, pbkdf2SHA256Prefix,
|
||||||
pbkdf2SHA512Prefix, sha512cryptPwdPrefix}
|
pbkdf2SHA512Prefix, sha512cryptPwdPrefix}
|
||||||
pbkdfPwdPrefixes = []string{pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix}
|
pbkdfPwdPrefixes = []string{pbkdf2SHA1Prefix, pbkdf2SHA256Prefix, pbkdf2SHA512Prefix}
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ const (
|
|||||||
PermDownload = "download"
|
PermDownload = "download"
|
||||||
// upload files is allowed
|
// upload files is allowed
|
||||||
PermUpload = "upload"
|
PermUpload = "upload"
|
||||||
|
// overwrite an existing file, while uploading, is allowed
|
||||||
|
// upload permission is required to allow file overwrite
|
||||||
|
PermOverwrite = "overwrite"
|
||||||
// delete files or directories is allowed
|
// delete files or directories is allowed
|
||||||
PermDelete = "delete"
|
PermDelete = "delete"
|
||||||
// rename files or directories is allowed
|
// rename files or directories is allowed
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ Let's see a sample usage for each REST API.
|
|||||||
Command:
|
Command:
|
||||||
|
|
||||||
```
|
```
|
||||||
python sftpgo_api_cli.py add-user test_username --password "test_pwd" --home-dir="/tmp/test_home_dir" --uid 33 --gid 1000 --max-sessions 2 --quota-size 0 --quota-files 3 --permissions "list" "download" "upload" "delete" "rename" "create_dirs" --upload-bandwidth 100 --download-bandwidth 60
|
python sftpgo_api_cli.py add-user test_username --password "test_pwd" --home-dir="/tmp/test_home_dir" --uid 33 --gid 1000 --max-sessions 2 --quota-size 0 --quota-files 3 --permissions "list" "download" "upload" "delete" "rename" "create_dirs" "overwrite" --upload-bandwidth 100 --download-bandwidth 60
|
||||||
```
|
```
|
||||||
|
|
||||||
Output:
|
Output:
|
||||||
@@ -62,7 +62,8 @@ Output:
|
|||||||
"upload",
|
"upload",
|
||||||
"delete",
|
"delete",
|
||||||
"rename",
|
"rename",
|
||||||
"create_dirs"
|
"create_dirs",
|
||||||
|
"overwrite"
|
||||||
],
|
],
|
||||||
"used_quota_size": 0,
|
"used_quota_size": 0,
|
||||||
"used_quota_files": 0,
|
"used_quota_files": 0,
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class SFTPGoApiRequests:
|
|||||||
download_bandwidth=0):
|
download_bandwidth=0):
|
||||||
user = {"id":user_id, "username":username, "uid":uid, "gid":gid,
|
user = {"id":user_id, "username":username, "uid":uid, "gid":gid,
|
||||||
"max_sessions":max_sessions, "quota_size":quota_size, "quota_files":quota_files,
|
"max_sessions":max_sessions, "quota_size":quota_size, "quota_files":quota_files,
|
||||||
"upload_bandwidth":upload_bandwidth,"download_bandwidth":download_bandwidth}
|
"upload_bandwidth":upload_bandwidth, "download_bandwidth":download_bandwidth}
|
||||||
if password:
|
if password:
|
||||||
user.update({"password":password})
|
user.update({"password":password})
|
||||||
if public_keys:
|
if public_keys:
|
||||||
@@ -136,7 +136,7 @@ def addCommonUserArguments(parser):
|
|||||||
help='Maximum size allowed as bytes. 0 means unlimited. Default: %(default)s')
|
help='Maximum size allowed as bytes. 0 means unlimited. Default: %(default)s')
|
||||||
parser.add_argument('-F', '--quota-files', type=int, default=0, help="default: %(default)s")
|
parser.add_argument('-F', '--quota-files', type=int, default=0, help="default: %(default)s")
|
||||||
parser.add_argument('-G', '--permissions', type=str, nargs='+', default=[],
|
parser.add_argument('-G', '--permissions', type=str, nargs='+', default=[],
|
||||||
choices=['*', 'list', 'download', 'upload', 'delete', 'rename', 'create_dirs',
|
choices=['*', 'list', 'download', 'upload', 'overwrite', 'delete', 'rename', 'create_dirs',
|
||||||
'create_symlinks'], help='Default: %(default)s')
|
'create_symlinks'], help='Default: %(default)s')
|
||||||
parser.add_argument('-U', '--upload-bandwidth', type=int, default=0,
|
parser.add_argument('-U', '--upload-bandwidth', type=int, default=0,
|
||||||
help='Maximum upload bandwidth as KB/s, 0 means unlimited. Default: %(default)s')
|
help='Maximum upload bandwidth as KB/s, 0 means unlimited. Default: %(default)s')
|
||||||
|
|||||||
@@ -131,6 +131,10 @@ func (c Connection) Filewrite(request *sftp.Request) (io.WriterAt, error) {
|
|||||||
return nil, sftp.ErrSshFxOpUnsupported
|
return nil, sftp.ErrSshFxOpUnsupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !c.User.HasPerm(dataprovider.PermOverwrite) {
|
||||||
|
return nil, sftp.ErrSshFxPermissionDenied
|
||||||
|
}
|
||||||
|
|
||||||
return c.handleSFTPUploadToExistingFile(request.Pflags(), p, filePath, stat.Size())
|
return c.handleSFTPUploadToExistingFile(request.Pflags(), p, filePath, stat.Size())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ func (c *scpCommand) handleUpload(uploadFilePath string, sizeToRead int64) error
|
|||||||
updateConnectionActivity(c.connection.ID)
|
updateConnectionActivity(c.connection.ID)
|
||||||
if !c.connection.User.HasPerm(dataprovider.PermUpload) {
|
if !c.connection.User.HasPerm(dataprovider.PermUpload) {
|
||||||
err := fmt.Errorf("Permission denied")
|
err := fmt.Errorf("Permission denied")
|
||||||
c.connection.Log(logger.LevelWarn, logSenderSCP, "error uploading file: %#v, permission denied", uploadFilePath)
|
c.connection.Log(logger.LevelWarn, logSenderSCP, "cannot upload file: %#v, permission denied", uploadFilePath)
|
||||||
c.sendErrorMessage(err.Error())
|
c.sendErrorMessage(err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -275,6 +275,13 @@ func (c *scpCommand) handleUpload(uploadFilePath string, sizeToRead int64) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !c.connection.User.HasPerm(dataprovider.PermOverwrite) {
|
||||||
|
err := fmt.Errorf("Permission denied")
|
||||||
|
c.connection.Log(logger.LevelWarn, logSenderSCP, "cannot overwrite file: %#v, permission denied", uploadFilePath)
|
||||||
|
c.sendErrorMessage(err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if uploadMode == uploadModeAtomic {
|
if uploadMode == uploadModeAtomic {
|
||||||
err = os.Rename(p, filePath)
|
err = os.Rename(p, filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1233,7 +1233,7 @@ func TestPermList(t *testing.T) {
|
|||||||
usePubKey := true
|
usePubKey := true
|
||||||
u := getTestUser(usePubKey)
|
u := getTestUser(usePubKey)
|
||||||
u.Permissions = []string{dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermDelete, dataprovider.PermRename,
|
u.Permissions = []string{dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermDelete, dataprovider.PermRename,
|
||||||
dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks}
|
dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks, dataprovider.PermOverwrite}
|
||||||
user, _, err := api.AddUser(u, http.StatusOK)
|
user, _, err := api.AddUser(u, http.StatusOK)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unable to add user: %v", err)
|
t.Errorf("unable to add user: %v", err)
|
||||||
@@ -1263,7 +1263,7 @@ func TestPermDownload(t *testing.T) {
|
|||||||
usePubKey := true
|
usePubKey := true
|
||||||
u := getTestUser(usePubKey)
|
u := getTestUser(usePubKey)
|
||||||
u.Permissions = []string{dataprovider.PermListItems, dataprovider.PermUpload, dataprovider.PermDelete, dataprovider.PermRename,
|
u.Permissions = []string{dataprovider.PermListItems, dataprovider.PermUpload, dataprovider.PermDelete, dataprovider.PermRename,
|
||||||
dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks}
|
dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks, dataprovider.PermOverwrite}
|
||||||
user, _, err := api.AddUser(u, http.StatusOK)
|
user, _, err := api.AddUser(u, http.StatusOK)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unable to add user: %v", err)
|
t.Errorf("unable to add user: %v", err)
|
||||||
@@ -1305,7 +1305,7 @@ func TestPermUpload(t *testing.T) {
|
|||||||
usePubKey := false
|
usePubKey := false
|
||||||
u := getTestUser(usePubKey)
|
u := getTestUser(usePubKey)
|
||||||
u.Permissions = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermDelete, dataprovider.PermRename,
|
u.Permissions = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermDelete, dataprovider.PermRename,
|
||||||
dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks}
|
dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks, dataprovider.PermOverwrite}
|
||||||
user, _, err := api.AddUser(u, http.StatusOK)
|
user, _, err := api.AddUser(u, http.StatusOK)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unable to add user: %v", err)
|
t.Errorf("unable to add user: %v", err)
|
||||||
@@ -1334,11 +1334,48 @@ func TestPermUpload(t *testing.T) {
|
|||||||
os.RemoveAll(user.GetHomeDir())
|
os.RemoveAll(user.GetHomeDir())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPermOverwrite(t *testing.T) {
|
||||||
|
usePubKey := false
|
||||||
|
u := getTestUser(usePubKey)
|
||||||
|
u.Permissions = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermDelete,
|
||||||
|
dataprovider.PermRename, dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks}
|
||||||
|
user, _, err := api.AddUser(u, http.StatusOK)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to add user: %v", err)
|
||||||
|
}
|
||||||
|
client, err := getSftpClient(user, usePubKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to create sftp client: %v", err)
|
||||||
|
} else {
|
||||||
|
defer client.Close()
|
||||||
|
testFileName := "test_file.dat"
|
||||||
|
testFilePath := filepath.Join(homeBasePath, testFileName)
|
||||||
|
testFileSize := int64(65535)
|
||||||
|
err = createTestFile(testFilePath, testFileSize)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to create test file: %v", err)
|
||||||
|
}
|
||||||
|
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error uploading file: %v", err)
|
||||||
|
}
|
||||||
|
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("file overwrite without permission should not succeed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err = api.RemoveUser(user, http.StatusOK)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to remove user: %v", err)
|
||||||
|
}
|
||||||
|
os.RemoveAll(user.GetHomeDir())
|
||||||
|
}
|
||||||
|
|
||||||
func TestPermDelete(t *testing.T) {
|
func TestPermDelete(t *testing.T) {
|
||||||
usePubKey := false
|
usePubKey := false
|
||||||
u := getTestUser(usePubKey)
|
u := getTestUser(usePubKey)
|
||||||
u.Permissions = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermRename,
|
u.Permissions = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermRename,
|
||||||
dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks}
|
dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks, dataprovider.PermOverwrite}
|
||||||
user, _, err := api.AddUser(u, http.StatusOK)
|
user, _, err := api.AddUser(u, http.StatusOK)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unable to add user: %v", err)
|
t.Errorf("unable to add user: %v", err)
|
||||||
@@ -1375,7 +1412,7 @@ func TestPermRename(t *testing.T) {
|
|||||||
usePubKey := false
|
usePubKey := false
|
||||||
u := getTestUser(usePubKey)
|
u := getTestUser(usePubKey)
|
||||||
u.Permissions = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermDelete,
|
u.Permissions = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermDelete,
|
||||||
dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks}
|
dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks, dataprovider.PermOverwrite}
|
||||||
user, _, err := api.AddUser(u, http.StatusOK)
|
user, _, err := api.AddUser(u, http.StatusOK)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unable to add user: %v", err)
|
t.Errorf("unable to add user: %v", err)
|
||||||
@@ -1416,7 +1453,7 @@ func TestPermCreateDirs(t *testing.T) {
|
|||||||
usePubKey := false
|
usePubKey := false
|
||||||
u := getTestUser(usePubKey)
|
u := getTestUser(usePubKey)
|
||||||
u.Permissions = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermDelete,
|
u.Permissions = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermDelete,
|
||||||
dataprovider.PermRename, dataprovider.PermCreateSymlinks}
|
dataprovider.PermRename, dataprovider.PermCreateSymlinks, dataprovider.PermOverwrite}
|
||||||
user, _, err := api.AddUser(u, http.StatusOK)
|
user, _, err := api.AddUser(u, http.StatusOK)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unable to add user: %v", err)
|
t.Errorf("unable to add user: %v", err)
|
||||||
@@ -1453,7 +1490,7 @@ func TestPermSymlink(t *testing.T) {
|
|||||||
usePubKey := false
|
usePubKey := false
|
||||||
u := getTestUser(usePubKey)
|
u := getTestUser(usePubKey)
|
||||||
u.Permissions = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermDelete,
|
u.Permissions = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermDelete,
|
||||||
dataprovider.PermRename, dataprovider.PermCreateDirs}
|
dataprovider.PermRename, dataprovider.PermCreateDirs, dataprovider.PermOverwrite}
|
||||||
user, _, err := api.AddUser(u, http.StatusOK)
|
user, _, err := api.AddUser(u, http.StatusOK)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unable to add user: %v", err)
|
t.Errorf("unable to add user: %v", err)
|
||||||
@@ -1794,6 +1831,47 @@ func TestSCPPermUpload(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSCPPermOverwrite(t *testing.T) {
|
||||||
|
if len(scpPath) == 0 {
|
||||||
|
t.Skip("scp command not found, unable to execute this test")
|
||||||
|
}
|
||||||
|
usePubKey := true
|
||||||
|
u := getTestUser(usePubKey)
|
||||||
|
u.Permissions = []string{dataprovider.PermUpload, dataprovider.PermCreateDirs}
|
||||||
|
user, _, err := api.AddUser(u, http.StatusOK)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to add user: %v", err)
|
||||||
|
}
|
||||||
|
testFileName := "test_file.dat"
|
||||||
|
testFilePath := filepath.Join(homeBasePath, testFileName)
|
||||||
|
testFileSize := int64(65536)
|
||||||
|
err = createTestFile(testFilePath, testFileSize)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to create test file: %v", err)
|
||||||
|
}
|
||||||
|
remoteUpPath := fmt.Sprintf("%v@127.0.0.1:%v", user.Username, "/tmp")
|
||||||
|
err = scpUpload(testFilePath, remoteUpPath, true, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("scp upload error: %v", err)
|
||||||
|
}
|
||||||
|
err = scpUpload(testFilePath, remoteUpPath, true, false)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("scp upload must fail, the user cannot ovewrite existing files")
|
||||||
|
}
|
||||||
|
err = os.Remove(testFilePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error removing test file")
|
||||||
|
}
|
||||||
|
err = os.RemoveAll(user.GetHomeDir())
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error removing uploaded files")
|
||||||
|
}
|
||||||
|
_, err = api.RemoveUser(user, http.StatusOK)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to remove user: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSCPPermDownload(t *testing.T) {
|
func TestSCPPermDownload(t *testing.T) {
|
||||||
if len(scpPath) == 0 {
|
if len(scpPath) == 0 {
|
||||||
t.Skip("scp command not found, unable to execute this test")
|
t.Skip("scp command not found, unable to execute this test")
|
||||||
|
|||||||
Reference in New Issue
Block a user