mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-25 09:00:27 +00:00
pre-upload action: add file open flags
Reading the flags the hook receiver can detect if the client wants to truncate the target file
This commit is contained in:
parent
c63b923ec3
commit
c1239fbf59
10 changed files with 33 additions and 26 deletions
|
@ -50,7 +50,7 @@ func InitializeActionHandler(handler ActionHandler) {
|
|||
}
|
||||
|
||||
// ExecutePreAction executes a pre-* action and returns the result
|
||||
func ExecutePreAction(user *dataprovider.User, operation, filePath, virtualPath, protocol string, fileSize int64) error {
|
||||
func ExecutePreAction(user *dataprovider.User, operation, filePath, virtualPath, protocol string, fileSize int64, openFlags int) error {
|
||||
if !utils.IsStringInSlice(operation, Config.Actions.ExecuteOn) {
|
||||
// for pre-delete we execute the internal handling on error, so we must return errUnconfiguredAction.
|
||||
// Other pre action will deny the operation on error so if we have no configuration we must return
|
||||
|
@ -60,13 +60,13 @@ func ExecutePreAction(user *dataprovider.User, operation, filePath, virtualPath,
|
|||
}
|
||||
return nil
|
||||
}
|
||||
notification := newActionNotification(user, operation, filePath, virtualPath, "", "", protocol, fileSize, nil)
|
||||
notification := newActionNotification(user, operation, filePath, virtualPath, "", "", protocol, fileSize, openFlags, nil)
|
||||
return actionHandler.Handle(notification)
|
||||
}
|
||||
|
||||
// ExecuteActionNotification executes the defined hook, if any, for the specified action
|
||||
func ExecuteActionNotification(user *dataprovider.User, operation, filePath, virtualPath, target, sshCmd, protocol string, fileSize int64, err error) {
|
||||
notification := newActionNotification(user, operation, filePath, virtualPath, target, sshCmd, protocol, fileSize, err)
|
||||
notification := newActionNotification(user, operation, filePath, virtualPath, target, sshCmd, protocol, fileSize, 0, err)
|
||||
|
||||
if utils.IsStringInSlice(operation, Config.Actions.ExecuteSync) {
|
||||
actionHandler.Handle(notification) //nolint:errcheck
|
||||
|
@ -94,12 +94,14 @@ type ActionNotification struct {
|
|||
Endpoint string `json:"endpoint,omitempty"`
|
||||
Status int `json:"status"`
|
||||
Protocol string `json:"protocol"`
|
||||
OpenFlags int `json:"open_flags,omitempty"`
|
||||
}
|
||||
|
||||
func newActionNotification(
|
||||
user *dataprovider.User,
|
||||
operation, filePath, virtualPath, target, sshCmd, protocol string,
|
||||
fileSize int64,
|
||||
openFlags int,
|
||||
err error,
|
||||
) *ActionNotification {
|
||||
var bucket, endpoint string
|
||||
|
@ -142,6 +144,7 @@ func newActionNotification(
|
|||
Endpoint: endpoint,
|
||||
Status: status,
|
||||
Protocol: protocol,
|
||||
OpenFlags: openFlags,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,5 +233,6 @@ func notificationAsEnvVars(notification *ActionNotification) []string {
|
|||
fmt.Sprintf("SFTPGO_ACTION_ENDPOINT=%v", notification.Endpoint),
|
||||
fmt.Sprintf("SFTPGO_ACTION_STATUS=%v", notification.Status),
|
||||
fmt.Sprintf("SFTPGO_ACTION_PROTOCOL=%v", notification.Protocol),
|
||||
fmt.Sprintf("SFTPGO_ACTION_OPEN_FLAGS=%v", notification.OpenFlags),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,38 +35,39 @@ func TestNewActionNotification(t *testing.T) {
|
|||
user.FsConfig.SFTPConfig = vfs.SFTPFsConfig{
|
||||
Endpoint: "sftpendpoint",
|
||||
}
|
||||
a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", ProtocolSFTP, 123, errors.New("fake error"))
|
||||
a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", ProtocolSFTP, 123, 0, errors.New("fake error"))
|
||||
assert.Equal(t, user.Username, a.Username)
|
||||
assert.Equal(t, 0, len(a.Bucket))
|
||||
assert.Equal(t, 0, len(a.Endpoint))
|
||||
assert.Equal(t, 0, a.Status)
|
||||
|
||||
user.FsConfig.Provider = vfs.S3FilesystemProvider
|
||||
a = newActionNotification(user, operationDownload, "path", "vpath", "target", "", ProtocolSSH, 123, nil)
|
||||
a = newActionNotification(user, operationDownload, "path", "vpath", "target", "", ProtocolSSH, 123, 0, nil)
|
||||
assert.Equal(t, "s3bucket", a.Bucket)
|
||||
assert.Equal(t, "endpoint", a.Endpoint)
|
||||
assert.Equal(t, 1, a.Status)
|
||||
|
||||
user.FsConfig.Provider = vfs.GCSFilesystemProvider
|
||||
a = newActionNotification(user, operationDownload, "path", "vpath", "target", "", ProtocolSCP, 123, ErrQuotaExceeded)
|
||||
a = newActionNotification(user, operationDownload, "path", "vpath", "target", "", ProtocolSCP, 123, 0, ErrQuotaExceeded)
|
||||
assert.Equal(t, "gcsbucket", a.Bucket)
|
||||
assert.Equal(t, 0, len(a.Endpoint))
|
||||
assert.Equal(t, 2, a.Status)
|
||||
|
||||
user.FsConfig.Provider = vfs.AzureBlobFilesystemProvider
|
||||
a = newActionNotification(user, operationDownload, "path", "vpath", "target", "", ProtocolSCP, 123, nil)
|
||||
a = newActionNotification(user, operationDownload, "path", "vpath", "target", "", ProtocolSCP, 123, 0, nil)
|
||||
assert.Equal(t, "azcontainer", a.Bucket)
|
||||
assert.Equal(t, "azsasurl", a.Endpoint)
|
||||
assert.Equal(t, 1, a.Status)
|
||||
|
||||
user.FsConfig.AzBlobConfig.SASURL = ""
|
||||
a = newActionNotification(user, operationDownload, "path", "vpath", "target", "", ProtocolSCP, 123, nil)
|
||||
a = newActionNotification(user, operationDownload, "path", "vpath", "target", "", ProtocolSCP, 123, os.O_APPEND, nil)
|
||||
assert.Equal(t, "azcontainer", a.Bucket)
|
||||
assert.Equal(t, "azendpoint", a.Endpoint)
|
||||
assert.Equal(t, 1, a.Status)
|
||||
assert.Equal(t, os.O_APPEND, a.OpenFlags)
|
||||
|
||||
user.FsConfig.Provider = vfs.SFTPFilesystemProvider
|
||||
a = newActionNotification(user, operationDownload, "path", "vpath", "target", "", ProtocolSFTP, 123, nil)
|
||||
a = newActionNotification(user, operationDownload, "path", "vpath", "target", "", ProtocolSFTP, 123, 0, nil)
|
||||
assert.Equal(t, "sftpendpoint", a.Endpoint)
|
||||
}
|
||||
|
||||
|
@ -80,7 +81,7 @@ func TestActionHTTP(t *testing.T) {
|
|||
user := &dataprovider.User{
|
||||
Username: "username",
|
||||
}
|
||||
a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", ProtocolSFTP, 123, nil)
|
||||
a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", ProtocolSFTP, 123, 0, nil)
|
||||
err := actionHandler.Handle(a)
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
@ -113,7 +114,7 @@ func TestActionCMD(t *testing.T) {
|
|||
user := &dataprovider.User{
|
||||
Username: "username",
|
||||
}
|
||||
a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", ProtocolSFTP, 123, nil)
|
||||
a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", ProtocolSFTP, 123, 0, nil)
|
||||
err = actionHandler.Handle(a)
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
@ -137,7 +138,7 @@ func TestWrongActions(t *testing.T) {
|
|||
Username: "username",
|
||||
}
|
||||
|
||||
a := newActionNotification(user, operationUpload, "", "", "", "", ProtocolSFTP, 123, nil)
|
||||
a := newActionNotification(user, operationUpload, "", "", "", "", ProtocolSFTP, 123, 0, nil)
|
||||
err := actionHandler.Handle(a)
|
||||
assert.Error(t, err, "action with bad command must fail")
|
||||
|
||||
|
|
|
@ -261,7 +261,7 @@ func (c *BaseConnection) RemoveFile(fs vfs.Fs, fsPath, virtualPath string, info
|
|||
}
|
||||
|
||||
size := info.Size()
|
||||
actionErr := ExecutePreAction(&c.User, operationPreDelete, fsPath, virtualPath, c.protocol, size)
|
||||
actionErr := ExecutePreAction(&c.User, operationPreDelete, fsPath, virtualPath, c.protocol, size, 0)
|
||||
if actionErr == nil {
|
||||
c.Log(logger.LevelDebug, "remove for path %#v handled by pre-delete action", fsPath)
|
||||
} else {
|
||||
|
|
|
@ -43,6 +43,7 @@ The external program can also read the following environment variables:
|
|||
- `SFTPGO_ACTION_ENDPOINT`, non-empty for S3, SFTP and Azure backend if configured. For Azure this is the SAS URL, if configured otherwise the endpoint
|
||||
- `SFTPGO_ACTION_STATUS`, integer. Status for `upload`, `download` and `ssh_cmd` actions. 0 means a generic error occurred. 1 means no error, 2 means quota exceeded error
|
||||
- `SFTPGO_ACTION_PROTOCOL`, string. Possible values are `SSH`, `SFTP`, `SCP`, `FTP`, `DAV`, `HTTP`
|
||||
- `SFTPGO_ACTION_OPEN_FLAGS`, integer. File open flags, can be non-zero for `pre-upload` action. If `SFTPGO_ACTION_FILE_SIZE` is greater than zero and `SFTPGO_ACTION_OPEN_FLAGS&512 == 0` the target file will not be truncated
|
||||
|
||||
Previous global environment variables aren't cleared when the script is called.
|
||||
The program must finish within 30 seconds.
|
||||
|
@ -60,6 +61,7 @@ If the `hook` defines an HTTP URL then this URL will be invoked as HTTP POST. Th
|
|||
- `endpoint`, included for S3, SFTP and Azure backend if configured. For Azure this is the SAS URL, if configured, otherwise the endpoint
|
||||
- `status`, integer. Status for `upload`, `download` and `ssh_cmd` actions. 0 means a generic error occurred. 1 means no error, 2 means quota exceeded error
|
||||
- `protocol`, string. Possible values are `SSH`, `SFTP`, `SCP`, `FTP`, `DAV`, `HTTP`
|
||||
- `open_flags`, integer, File open flags, can be non-zero for `pre-upload` action. If `file_size` is greater than zero and `file_size&512 == 0` the target file will not be truncated
|
||||
|
||||
The HTTP hook will use the global configuration for HTTP clients and will respect the retry configurations.
|
||||
|
||||
|
|
|
@ -297,7 +297,7 @@ func (c *Connection) downloadFile(fs vfs.Fs, fsPath, ftpPath string, offset int6
|
|||
return nil, c.GetPermissionDeniedError()
|
||||
}
|
||||
|
||||
if err := common.ExecutePreAction(&c.User, common.OperationPreDownload, fsPath, ftpPath, c.GetProtocol(), 0); err != nil {
|
||||
if err := common.ExecutePreAction(&c.User, common.OperationPreDownload, fsPath, ftpPath, c.GetProtocol(), 0, 0); err != nil {
|
||||
c.Log(logger.LevelDebug, "download for file %#v denied by pre action: %v", ftpPath, err)
|
||||
return nil, c.GetPermissionDeniedError()
|
||||
}
|
||||
|
@ -358,7 +358,7 @@ func (c *Connection) handleFTPUploadToNewFile(fs vfs.Fs, resolvedPath, filePath,
|
|||
c.Log(logger.LevelInfo, "denying file write due to quota limits")
|
||||
return nil, common.ErrQuotaExceeded
|
||||
}
|
||||
if err := common.ExecutePreAction(&c.User, common.OperationPreUpload, resolvedPath, requestPath, c.GetProtocol(), 0); err != nil {
|
||||
if err := common.ExecutePreAction(&c.User, common.OperationPreUpload, resolvedPath, requestPath, c.GetProtocol(), 0, 0); err != nil {
|
||||
c.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
|
||||
return nil, c.GetPermissionDeniedError()
|
||||
}
|
||||
|
@ -388,7 +388,7 @@ func (c *Connection) handleFTPUploadToExistingFile(fs vfs.Fs, flags int, resolve
|
|||
c.Log(logger.LevelInfo, "denying file write due to quota limits")
|
||||
return nil, common.ErrQuotaExceeded
|
||||
}
|
||||
if err := common.ExecutePreAction(&c.User, common.OperationPreUpload, resolvedPath, requestPath, c.GetProtocol(), fileSize); err != nil {
|
||||
if err := common.ExecutePreAction(&c.User, common.OperationPreUpload, resolvedPath, requestPath, c.GetProtocol(), fileSize, flags); err != nil {
|
||||
c.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
|
||||
return nil, c.GetPermissionDeniedError()
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ func (c *Connection) getFileReader(name string, offset int64, method string) (io
|
|||
}
|
||||
|
||||
if method != http.MethodHead {
|
||||
if err := common.ExecutePreAction(&c.User, common.OperationPreDownload, p, name, c.GetProtocol(), 0); err != nil {
|
||||
if err := common.ExecutePreAction(&c.User, common.OperationPreDownload, p, name, c.GetProtocol(), 0, 0); err != nil {
|
||||
c.Log(logger.LevelDebug, "download for file %#v denied by pre action: %v", name, err)
|
||||
return nil, c.GetPermissionDeniedError()
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ func (c *Connection) Fileread(request *sftp.Request) (io.ReaderAt, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if err := common.ExecutePreAction(&c.User, common.OperationPreDownload, p, request.Filepath, c.GetProtocol(), 0); err != nil {
|
||||
if err := common.ExecutePreAction(&c.User, common.OperationPreDownload, p, request.Filepath, c.GetProtocol(), 0, 0); err != nil {
|
||||
c.Log(logger.LevelDebug, "download for file %#v denied by pre action: %v", request.Filepath, err)
|
||||
return nil, c.GetPermissionDeniedError()
|
||||
}
|
||||
|
@ -330,7 +330,7 @@ func (c *Connection) handleSFTPUploadToNewFile(fs vfs.Fs, resolvedPath, filePath
|
|||
return nil, sftp.ErrSSHFxFailure
|
||||
}
|
||||
|
||||
if err := common.ExecutePreAction(&c.User, common.OperationPreUpload, resolvedPath, requestPath, c.GetProtocol(), 0); err != nil {
|
||||
if err := common.ExecutePreAction(&c.User, common.OperationPreUpload, resolvedPath, requestPath, c.GetProtocol(), 0, 0); err != nil {
|
||||
c.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
|
||||
return nil, c.GetPermissionDeniedError()
|
||||
}
|
||||
|
@ -361,13 +361,13 @@ func (c *Connection) handleSFTPUploadToExistingFile(fs vfs.Fs, pflags sftp.FileO
|
|||
c.Log(logger.LevelInfo, "denying file write due to quota limits")
|
||||
return nil, sftp.ErrSSHFxFailure
|
||||
}
|
||||
if err := common.ExecutePreAction(&c.User, common.OperationPreUpload, resolvedPath, requestPath, c.GetProtocol(), fileSize); err != nil {
|
||||
osFlags := getOSOpenFlags(pflags)
|
||||
if err := common.ExecutePreAction(&c.User, common.OperationPreUpload, resolvedPath, requestPath, c.GetProtocol(), fileSize, osFlags); err != nil {
|
||||
c.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
|
||||
return nil, c.GetPermissionDeniedError()
|
||||
}
|
||||
|
||||
minWriteOffset := int64(0)
|
||||
osFlags := getOSOpenFlags(pflags)
|
||||
isTruncate := osFlags&os.O_TRUNC != 0
|
||||
// for upload resumes OpenSSH sets the APPEND flag while WinSCP does not set it,
|
||||
// so we suppose this is an upload resume if the TRUNCATE flag is not set
|
||||
|
|
|
@ -219,7 +219,7 @@ func (c *scpCommand) handleUploadFile(fs vfs.Fs, resolvedPath, filePath string,
|
|||
c.sendErrorMessage(fs, err)
|
||||
return err
|
||||
}
|
||||
err := common.ExecutePreAction(&c.connection.User, common.OperationPreUpload, resolvedPath, requestPath, c.connection.GetProtocol(), fileSize)
|
||||
err := common.ExecutePreAction(&c.connection.User, common.OperationPreUpload, resolvedPath, requestPath, c.connection.GetProtocol(), fileSize, os.O_TRUNC)
|
||||
if err != nil {
|
||||
c.connection.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
|
||||
err = c.connection.GetPermissionDeniedError()
|
||||
|
@ -514,7 +514,7 @@ func (c *scpCommand) handleDownload(filePath string) error {
|
|||
return common.ErrPermissionDenied
|
||||
}
|
||||
|
||||
if err := common.ExecutePreAction(&c.connection.User, common.OperationPreDownload, p, filePath, c.connection.GetProtocol(), 0); err != nil {
|
||||
if err := common.ExecutePreAction(&c.connection.User, common.OperationPreDownload, p, filePath, c.connection.GetProtocol(), 0, 0); err != nil {
|
||||
c.connection.Log(logger.LevelDebug, "download for file %#v denied by pre action: %v", filePath, err)
|
||||
c.sendErrorMessage(fs, common.ErrPermissionDenied)
|
||||
return common.ErrPermissionDenied
|
||||
|
|
|
@ -148,7 +148,7 @@ func (f *webDavFile) Read(p []byte) (n int, err error) {
|
|||
return 0, f.Connection.GetPermissionDeniedError()
|
||||
}
|
||||
err := common.ExecutePreAction(&f.Connection.User, common.OperationPreDownload, f.GetFsPath(), f.GetVirtualPath(),
|
||||
f.Connection.GetProtocol(), 0)
|
||||
f.Connection.GetProtocol(), 0, 0)
|
||||
if err != nil {
|
||||
f.Connection.Log(logger.LevelDebug, "download for file %#v denied by pre action: %v", f.GetVirtualPath(), err)
|
||||
return 0, f.Connection.GetPermissionDeniedError()
|
||||
|
|
|
@ -194,7 +194,7 @@ func (c *Connection) handleUploadToNewFile(fs vfs.Fs, resolvedPath, filePath, re
|
|||
c.Log(logger.LevelInfo, "denying file write due to quota limits")
|
||||
return nil, common.ErrQuotaExceeded
|
||||
}
|
||||
if err := common.ExecutePreAction(&c.User, common.OperationPreUpload, resolvedPath, requestPath, c.GetProtocol(), 0); err != nil {
|
||||
if err := common.ExecutePreAction(&c.User, common.OperationPreUpload, resolvedPath, requestPath, c.GetProtocol(), 0, 0); err != nil {
|
||||
c.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
|
||||
return nil, c.GetPermissionDeniedError()
|
||||
}
|
||||
|
@ -223,7 +223,7 @@ func (c *Connection) handleUploadToExistingFile(fs vfs.Fs, resolvedPath, filePat
|
|||
c.Log(logger.LevelInfo, "denying file write due to quota limits")
|
||||
return nil, common.ErrQuotaExceeded
|
||||
}
|
||||
if err := common.ExecutePreAction(&c.User, common.OperationPreUpload, resolvedPath, requestPath, c.GetProtocol(), fileSize); err != nil {
|
||||
if err := common.ExecutePreAction(&c.User, common.OperationPreUpload, resolvedPath, requestPath, c.GetProtocol(), fileSize, os.O_TRUNC); err != nil {
|
||||
c.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
|
||||
return nil, c.GetPermissionDeniedError()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue