eventmanager: add support for global star path matching

This introduce a backward incompatible change for filesystem path matching
in the Event Manager, now patterns like "*.txt" will no longer match any
file with the "txt" suffix, you need to change them to "/**/*.txt".

Also change pre-delete behaviour, now if an error is returned the client
will get a permission denied error. This is the same as the other pre-*
action. Previously it was not possible to deny deletion of a file.

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino 2023-01-02 15:59:00 +01:00
parent 2611dd2c98
commit 7fa0959af4
No known key found for this signature in database
GPG key ID: 935D2952DEC4EECF
17 changed files with 179 additions and 85 deletions

View file

@ -28,9 +28,7 @@ For cloud backends directories are virtual, they are created implicitly when you
The notification will indicate if an error is detected and so, for example, a partial file is uploaded.
The `pre-delete` action, if defined, will be called just before files deletion. If the external command completes with a zero exit status or the HTTP notification response code is `200`, SFTPGo will assume that the file was already deleted/moved and so it will not try to remove the file and it will not execute the hook defined for the `delete` action.
The `pre-download` and `pre-upload` actions, will be called before downloads and uploads. If the external command completes with a zero exit status or the HTTP notification response code is `200`, SFTPGo will allow the operation, otherwise the client will get a permission denied error.
The `pre-delete`, `pre-download` and `pre-upload` actions, will be called before deleting, downloading and uploading files. If the external command completes with a zero exit status or the HTTP notification response code is `200`, SFTPGo will allow the operation, otherwise the client will get a permission denied error.
If the `hook` defines a path to an external program, then this program can read the following environment variables:

View file

@ -63,7 +63,7 @@ Actions are executed in a sequential order except for sync actions that are exec
- `Stop on failure`, the next action will not be executed if the current one fails.
- `Failure action`, this action will be executed only if at least another one fails. :warning: Please note that a failure action isn't executed if the event fails, for example if a download fails the main action is executed. The failure action is executed only if one of the non-failure actions associated to a rule fails.
- `Execute sync`, for upload events, you can execute the action(s) synchronously. Executing an action synchronously means that SFTPGo will not return a result code to the client (which is waiting for it) until your action have completed its execution. If your acion takes a long time to complete this could cause a timeout on the client side, which wouldn't receive the server response in a timely manner and eventually drop the connection. For pre-* events at least a sync action is required. If pre-delete sync action(s) completes successfully, SFTPGo will assume that the file was already deleted/moved and so it will not try to remove the file and it will not execute any defined `delete` actions. If pre-upload/download action(s) completes successfully, SFTPGo will allow the operation, otherwise the client will get a permission denied error.
- `Execute sync`, for upload events, you can execute the action(s) synchronously. Executing an action synchronously means that SFTPGo will not return a result code to the client (which is waiting for it) until your action have completed its execution. If your acion takes a long time to complete this could cause a timeout on the client side, which wouldn't receive the server response in a timely manner and eventually drop the connection. For pre-* events at least a sync action is required. If pre-delete,pre-upload, pre-download sync action(s) completes successfully, SFTPGo will allow the operation, otherwise the client will get a permission denied error.
If you are running multiple SFTPGo instances connected to the same data provider, you can choose whether to allow simultaneous execution for scheduled actions.

1
go.mod
View file

@ -17,6 +17,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/s3 v1.29.6
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.17.0
github.com/aws/aws-sdk-go-v2/service/sts v1.17.7
github.com/bmatcuk/doublestar/v4 v4.4.0
github.com/cockroachdb/cockroach-go/v2 v2.2.20
github.com/coreos/go-oidc/v3 v3.4.0
github.com/drakkan/webdav v0.0.0-20221101181759-17ed21f9337b

2
go.sum
View file

@ -304,6 +304,8 @@ github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edY
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/bmatcuk/doublestar/v4 v4.4.0 h1:LmAwNwhjEbYtyVLzjcP/XeVw4nhuScHGkF/XWXnvIic=
github.com/bmatcuk/doublestar/v4 v4.4.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=

View file

@ -41,8 +41,6 @@ import (
)
var (
errUnconfiguredAction = errors.New("no hook is configured for this action")
errNoHook = errors.New("unable to execute action, no hook defined")
errUnexpectedHTTResponse = errors.New("unexpected HTTP hook response code")
hooksConcurrencyGuard = make(chan struct{}, 150)
activeHooks atomic.Int32
@ -80,24 +78,18 @@ func InitializeActionHandler(handler ActionHandler) {
actionHandler = handler
}
func handleUnconfiguredPreAction(operation string) error {
// 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
// a nil error
if operation == operationPreDelete {
return errUnconfiguredAction
}
return nil
}
// ExecutePreAction executes a pre-* action and returns the result
func ExecutePreAction(conn *BaseConnection, operation, filePath, virtualPath string, fileSize int64, openFlags int) error {
// ExecutePreAction executes a pre-* action and returns the result.
// The returned status has the following meaning:
// - 0 not executed
// - 1 executed using an external hook
// - 2 executed using the event manager
func ExecutePreAction(conn *BaseConnection, operation, filePath, virtualPath string, fileSize int64, openFlags int) (int, error) {
var event *notifier.FsEvent
hasNotifiersPlugin := plugin.Handler.HasNotifiers()
hasHook := util.Contains(Config.Actions.ExecuteOn, operation)
hasRules := eventManager.hasFsRules()
if !hasHook && !hasNotifiersPlugin && !hasRules {
return handleUnconfiguredPreAction(operation)
return 0, nil
}
event = newActionNotification(&conn.User, operation, filePath, virtualPath, "", "", "",
conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, openFlags, conn.getNotificationStatus(nil))
@ -124,11 +116,11 @@ func ExecutePreAction(conn *BaseConnection, operation, filePath, virtualPath str
}
executedSync, err := eventManager.handleFsEvent(params)
if executedSync {
return err
return 2, err
}
}
if !hasHook {
return handleUnconfiguredPreAction(operation)
return 0, nil
}
return actionHandler.Handle(event)
}
@ -176,7 +168,8 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua
}
if hasHook {
if util.Contains(Config.Actions.ExecuteSync, operation) {
return actionHandler.Handle(notification)
_, err := actionHandler.Handle(notification)
return err
}
go func() {
startNewHook()
@ -190,7 +183,7 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua
// ActionHandler handles a notification for a Protocol Action.
type ActionHandler interface {
Handle(notification *notifier.FsEvent) error
Handle(notification *notifier.FsEvent) (int, error)
}
func newActionNotification(
@ -244,28 +237,30 @@ func newActionNotification(
type defaultActionHandler struct{}
func (h *defaultActionHandler) Handle(event *notifier.FsEvent) error {
func (h *defaultActionHandler) Handle(event *notifier.FsEvent) (int, error) {
if !util.Contains(Config.Actions.ExecuteOn, event.Action) {
return errUnconfiguredAction
return 0, nil
}
if Config.Actions.Hook == "" {
logger.Warn(event.Protocol, "", "Unable to send notification, no hook is defined")
return errNoHook
return 0, nil
}
if strings.HasPrefix(Config.Actions.Hook, "http") {
return h.handleHTTP(event)
err := h.handleHTTP(event)
return 1, err
}
return h.handleCommand(event)
err := h.handleCommand(event)
return 1, err
}
func (h *defaultActionHandler) handleHTTP(event *notifier.FsEvent) error {
u, err := url.Parse(Config.Actions.Hook)
if err != nil {
logger.Error(event.Protocol, "", "Invalid hook %#v for operation %#v: %v",
logger.Error(event.Protocol, "", "Invalid hook %q for operation %q: %v",
Config.Actions.Hook, event.Action, err)
return err
}
@ -294,7 +289,7 @@ func (h *defaultActionHandler) handleHTTP(event *notifier.FsEvent) error {
func (h *defaultActionHandler) handleCommand(event *notifier.FsEvent) error {
if !filepath.IsAbs(Config.Actions.Hook) {
err := fmt.Errorf("invalid notification command %#v", Config.Actions.Hook)
err := fmt.Errorf("invalid notification command %q", Config.Actions.Hook)
logger.Warn(event.Protocol, "", "unable to execute notification command: %v", err)
return err

View file

@ -136,18 +136,21 @@ func TestActionHTTP(t *testing.T) {
}
a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "",
xid.New().String(), 123, 0, 1)
err := actionHandler.Handle(a)
status, err := actionHandler.Handle(a)
assert.NoError(t, err)
assert.Equal(t, 1, status)
Config.Actions.Hook = "http://invalid:1234"
err = actionHandler.Handle(a)
status, err = actionHandler.Handle(a)
assert.Error(t, err)
assert.Equal(t, 1, status)
Config.Actions.Hook = fmt.Sprintf("http://%v/404", httpAddr)
err = actionHandler.Handle(a)
status, err = actionHandler.Handle(a)
if assert.Error(t, err) {
assert.EqualError(t, err, errUnexpectedHTTResponse.Error())
}
assert.Equal(t, 1, status)
Config.Actions = actionsCopy
}
@ -173,8 +176,9 @@ func TestActionCMD(t *testing.T) {
sessionID := shortuuid.New()
a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID,
123, 0, 1)
err = actionHandler.Handle(a)
status, err := actionHandler.Handle(a)
assert.NoError(t, err)
assert.Equal(t, 1, status)
c := NewBaseConnection("id", ProtocolSFTP, "", "", *user)
err = ExecuteActionNotification(c, OperationSSHCmd, "path", "vpath", "target", "vtarget", "sha1sum", 0, nil)
@ -205,29 +209,32 @@ func TestWrongActions(t *testing.T) {
a := newActionNotification(user, operationUpload, "", "", "", "", "", ProtocolSFTP, "", xid.New().String(),
123, 0, 1)
err := actionHandler.Handle(a)
status, err := actionHandler.Handle(a)
assert.Error(t, err, "action with bad command must fail")
assert.Equal(t, 1, status)
a.Action = operationDelete
err = actionHandler.Handle(a)
assert.EqualError(t, err, errUnconfiguredAction.Error())
status, err = actionHandler.Handle(a)
assert.NoError(t, err)
assert.Equal(t, 0, status)
Config.Actions.Hook = "http://foo\x7f.com/"
a.Action = operationUpload
err = actionHandler.Handle(a)
status, err = actionHandler.Handle(a)
assert.Error(t, err, "action with bad url must fail")
assert.Equal(t, 1, status)
Config.Actions.Hook = ""
err = actionHandler.Handle(a)
if assert.Error(t, err) {
assert.EqualError(t, err, errNoHook.Error())
}
status, err = actionHandler.Handle(a)
assert.NoError(t, err)
assert.Equal(t, 0, status)
Config.Actions.Hook = "relative path"
err = actionHandler.Handle(a)
status, err = actionHandler.Handle(a)
if assert.Error(t, err) {
assert.EqualError(t, err, fmt.Sprintf("invalid notification command %#v", Config.Actions.Hook))
assert.EqualError(t, err, fmt.Sprintf("invalid notification command %q", Config.Actions.Hook))
}
assert.Equal(t, 1, status)
Config.Actions = actionsCopy
}
@ -242,7 +249,7 @@ func TestPreDeleteAction(t *testing.T) {
assert.NoError(t, err)
Config.Actions = ProtocolActions{
ExecuteOn: []string{operationPreDelete},
Hook: hookCmd,
Hook: "missing hook",
}
homeDir := filepath.Join(os.TempDir(), "test_user")
err = os.MkdirAll(homeDir, os.ModePerm)
@ -264,8 +271,12 @@ func TestPreDeleteAction(t *testing.T) {
info, err := os.Stat(testfile)
assert.NoError(t, err)
err = c.RemoveFile(fs, testfile, "testfile", info)
assert.NoError(t, err)
assert.ErrorIs(t, err, c.GetPermissionDeniedError())
assert.FileExists(t, testfile)
Config.Actions.Hook = hookCmd
err = c.RemoveFile(fs, testfile, "testfile", info)
assert.NoError(t, err)
assert.NoFileExists(t, testfile)
os.RemoveAll(homeDir)
@ -289,10 +300,12 @@ func TestUnconfiguredHook(t *testing.T) {
assert.True(t, plugin.Handler.HasNotifiers())
c := NewBaseConnection("id", ProtocolSFTP, "", "", dataprovider.User{})
err = ExecutePreAction(c, OperationPreDownload, "", "", 0, 0)
status, err := ExecutePreAction(c, OperationPreDownload, "", "", 0, 0)
assert.NoError(t, err)
err = ExecutePreAction(c, operationPreDelete, "", "", 0, 0)
assert.ErrorIs(t, err, errUnconfiguredAction)
assert.Equal(t, status, 0)
status, err = ExecutePreAction(c, operationPreDelete, "", "", 0, 0)
assert.NoError(t, err)
assert.Equal(t, status, 0)
err = ExecuteActionNotification(c, operationDownload, "", "", "", "", "", 0, nil)
assert.NoError(t, err)
@ -308,10 +321,10 @@ type actionHandlerStub struct {
called bool
}
func (h *actionHandlerStub) Handle(event *notifier.FsEvent) error {
func (h *actionHandlerStub) Handle(event *notifier.FsEvent) (int, error) {
h.called = true
return nil
return 1, nil
}
func TestInitializeActionHandler(t *testing.T) {
@ -322,8 +335,8 @@ func TestInitializeActionHandler(t *testing.T) {
InitializeActionHandler(&defaultActionHandler{})
})
err := actionHandler.Handle(&notifier.FsEvent{})
status, err := actionHandler.Handle(&notifier.FsEvent{})
assert.NoError(t, err)
assert.True(t, handler.called)
assert.Equal(t, 1, status)
}

View file

@ -391,11 +391,18 @@ func (c *BaseConnection) RemoveFile(fs vfs.Fs, fsPath, virtualPath string, info
}
size := info.Size()
actionErr := ExecutePreAction(c, operationPreDelete, fsPath, virtualPath, size, 0)
if actionErr == nil {
c.Log(logger.LevelDebug, "remove for path %q handled by pre-delete action", fsPath)
} else {
if err := fs.Remove(fsPath, false); err != nil {
status, err := ExecutePreAction(c, operationPreDelete, fsPath, virtualPath, size, 0)
if err != nil {
c.Log(logger.LevelDebug, "delete for file %q denied by pre action: %v", virtualPath, err)
return c.GetPermissionDeniedError()
}
updateQuota := true
if err := fs.Remove(fsPath, false); err != nil {
if status > 0 && fs.IsNotExist(err) {
// file removed in the pre-action, if the file was deleted from the EventManager the quota is already updated
c.Log(logger.LevelDebug, "file deleted from the hook, status: %d", status)
updateQuota = (status == 1)
} else {
c.Log(logger.LevelError, "failed to remove file/symlink %q: %+v", fsPath, err)
return c.GetFsError(fs, err)
}
@ -403,7 +410,7 @@ func (c *BaseConnection) RemoveFile(fs vfs.Fs, fsPath, virtualPath string, info
logger.CommandLog(removeLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "", -1,
c.localAddr, c.remoteAddr)
if info.Mode()&os.ModeSymlink == 0 {
if updateQuota && info.Mode()&os.ModeSymlink == 0 {
vfolder, err := c.User.GetVirtualFolderForPath(path.Dir(virtualPath))
if err == nil {
dataprovider.UpdateVirtualFolderQuota(&vfolder.BaseVirtualFolder, -1, -size, false) //nolint:errcheck
@ -414,9 +421,7 @@ func (c *BaseConnection) RemoveFile(fs vfs.Fs, fsPath, virtualPath string, info
dataprovider.UpdateUserQuota(&c.User, -1, -size, false) //nolint:errcheck
}
}
if actionErr != nil {
ExecuteActionNotification(c, operationDelete, fsPath, virtualPath, "", "", "", size, nil) //nolint:errcheck
}
ExecuteActionNotification(c, operationDelete, fsPath, virtualPath, "", "", "", size, nil) //nolint:errcheck
return nil
}

View file

@ -36,6 +36,7 @@ import (
"sync/atomic"
"time"
"github.com/bmatcuk/doublestar/v4"
"github.com/klauspost/compress/zip"
"github.com/robfig/cron/v3"
"github.com/rs/xid"
@ -285,9 +286,7 @@ func (r *eventRulesContainer) checkFsEventMatch(conditions dataprovider.EventCon
return false
}
if !checkEventConditionPatterns(params.VirtualPath, conditions.Options.FsPaths) {
if !checkEventConditionPatterns(params.ObjectName, conditions.Options.FsPaths) {
return false
}
return false
}
if len(conditions.Options.Protocols) > 0 && !util.Contains(conditions.Options.Protocols, params.Protocol) {
return false
@ -966,7 +965,13 @@ func replaceWithReplacer(input string, replacer *strings.Replacer) string {
}
func checkEventConditionPattern(p dataprovider.ConditionPattern, name string) bool {
matched, err := path.Match(p.Pattern, name)
var matched bool
var err error
if strings.Contains(p.Pattern, "**") {
matched, err = doublestar.Match(p.Pattern, name)
} else {
matched, err = path.Match(p.Pattern, name)
}
if err != nil {
eventManagerLog(logger.LevelError, "pattern matching error %q, err: %v", p.Pattern, err)
return false

View file

@ -119,7 +119,7 @@ func TestEventRuleMatch(t *testing.T) {
},
FsPaths: []dataprovider.ConditionPattern{
{
Pattern: "*.txt",
Pattern: "/**/*.txt",
},
},
Protocols: []string{ProtocolSFTP},
@ -268,6 +268,40 @@ func TestEventRuleMatch(t *testing.T) {
assert.False(t, res)
}
func TestDoubleStarMatching(t *testing.T) {
c := dataprovider.ConditionPattern{
Pattern: "/mydir/**",
}
res := checkEventConditionPattern(c, "/mydir")
assert.True(t, res)
res = checkEventConditionPattern(c, "/mydirname")
assert.False(t, res)
res = checkEventConditionPattern(c, "/mydir/sub")
assert.True(t, res)
res = checkEventConditionPattern(c, "/mydir/sub/dir")
assert.True(t, res)
c.Pattern = "/**/*"
res = checkEventConditionPattern(c, "/mydir")
assert.True(t, res)
res = checkEventConditionPattern(c, "/mydirname")
assert.True(t, res)
res = checkEventConditionPattern(c, "/mydir/sub/dir/file.txt")
assert.True(t, res)
c.Pattern = "/mydir/**/*.txt"
res = checkEventConditionPattern(c, "/mydir")
assert.False(t, res)
res = checkEventConditionPattern(c, "/mydirname/f.txt")
assert.False(t, res)
res = checkEventConditionPattern(c, "/mydir/sub")
assert.False(t, res)
res = checkEventConditionPattern(c, "/mydir/sub/dir")
assert.False(t, res)
res = checkEventConditionPattern(c, "/mydir/sub/dir/a.txt")
assert.True(t, res)
}
func TestEventManager(t *testing.T) {
startEventScheduler()
action := &dataprovider.BaseEventAction{

View file

@ -3470,7 +3470,7 @@ func TestEventRule(t *testing.T) {
Pattern: "/subdir/*.dat",
},
{
Pattern: "*.txt",
Pattern: "/**/*.txt",
},
},
},
@ -3520,7 +3520,7 @@ func TestEventRule(t *testing.T) {
Options: dataprovider.ConditionOptions{
FsPaths: []dataprovider.ConditionPattern{
{
Pattern: "*.dat",
Pattern: "/**/*.dat",
},
},
},
@ -4231,6 +4231,14 @@ func TestEventRulePreDelete(t *testing.T) {
Trigger: dataprovider.EventTriggerFsEvent,
Conditions: dataprovider.EventConditions{
FsEvents: []string{"pre-delete"},
Options: dataprovider.ConditionOptions{
FsPaths: []dataprovider.ConditionPattern{
{
Pattern: fmt.Sprintf("/%s/**", movePath),
InverseMatch: true,
},
},
},
},
Actions: []dataprovider.EventAction{
{
@ -4255,7 +4263,19 @@ func TestEventRulePreDelete(t *testing.T) {
}
rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
assert.NoError(t, err)
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
u := getTestUser()
u.QuotaFiles = 1000
u.VirtualFolders = []vfs.VirtualFolder{
{
BaseVirtualFolder: vfs.BaseVirtualFolder{
Name: movePath,
MappedPath: filepath.Join(os.TempDir(), movePath),
},
VirtualPath: "/" + movePath,
QuotaFiles: 1000,
},
}
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
conn, client, err := getSftpClient(user)
if assert.NoError(t, err) {
@ -4273,7 +4293,7 @@ func TestEventRulePreDelete(t *testing.T) {
assert.NoError(t, err)
err = client.Remove(path.Join(testDir, testFileName))
assert.NoError(t, err)
// check
// check files
_, err = client.Stat(testFileName)
assert.ErrorIs(t, err, os.ErrNotExist)
_, err = client.Stat(path.Join(testDir, testFileName))
@ -4282,6 +4302,23 @@ func TestEventRulePreDelete(t *testing.T) {
assert.NoError(t, err)
_, err = client.Stat(path.Join("/", movePath, testDir, testFileName))
assert.NoError(t, err)
// check quota
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, 0, user.UsedQuotaFiles)
assert.Equal(t, int64(0), user.UsedQuotaSize)
folder, _, err := httpdtest.GetFolderByName(movePath, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, 2, folder.UsedQuotaFiles)
assert.Equal(t, int64(200), folder.UsedQuotaSize)
// pre-delete action is not executed in movePath
err = client.Remove(path.Join("/", movePath, testFileName))
assert.NoError(t, err)
// check quota
folder, _, err = httpdtest.GetFolderByName(movePath, http.StatusOK)
assert.NoError(t, err)
assert.Equal(t, 1, folder.UsedQuotaFiles)
assert.Equal(t, int64(100), folder.UsedQuotaSize)
}
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
@ -4294,6 +4331,10 @@ func TestEventRulePreDelete(t *testing.T) {
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: movePath}, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(filepath.Join(os.TempDir(), movePath))
assert.NoError(t, err)
}
func TestEventRulePreDownloadUpload(t *testing.T) {

View file

@ -342,7 +342,7 @@ func (c *Connection) downloadFile(fs vfs.Fs, fsPath, ftpPath string, offset int6
return nil, c.GetErrorForDeniedFile(policy)
}
if err := common.ExecutePreAction(c.BaseConnection, common.OperationPreDownload, fsPath, ftpPath, 0, 0); err != nil {
if _, err := common.ExecutePreAction(c.BaseConnection, common.OperationPreDownload, fsPath, ftpPath, 0, 0); err != nil {
c.Log(logger.LevelDebug, "download for file %#v denied by pre action: %v", ftpPath, err)
return nil, c.GetPermissionDeniedError()
}
@ -404,7 +404,7 @@ func (c *Connection) handleFTPUploadToNewFile(fs vfs.Fs, flags int, resolvedPath
c.Log(logger.LevelInfo, "denying file write due to quota limits")
return nil, ftpserver.ErrStorageExceeded
}
if err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, 0, 0); err != nil {
if _, err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, 0, 0); err != nil {
c.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
return nil, fmt.Errorf("%w, denied by pre-upload action", ftpserver.ErrFileNameNotAllowed)
}
@ -449,7 +449,7 @@ func (c *Connection) handleFTPUploadToExistingFile(fs vfs.Fs, flags int, resolve
c.Log(logger.LevelDebug, "unable to get max write size: %v", err)
return nil, err
}
if err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, fileSize, flags); err != nil {
if _, err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, fileSize, flags); err != nil {
c.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
return nil, fmt.Errorf("%w, denied by pre-upload action", ftpserver.ErrFileNameNotAllowed)
}

View file

@ -118,7 +118,7 @@ func (c *Connection) getFileReader(name string, offset int64, method string) (io
}
if method != http.MethodHead {
if err := common.ExecutePreAction(c.BaseConnection, common.OperationPreDownload, p, name, 0, 0); err != nil {
if _, err := common.ExecutePreAction(c.BaseConnection, common.OperationPreDownload, p, name, 0, 0); err != nil {
c.Log(logger.LevelDebug, "download for file %#v denied by pre action: %v", name, err)
return nil, c.GetPermissionDeniedError()
}
@ -193,7 +193,7 @@ func (c *Connection) handleUploadFile(fs vfs.Fs, resolvedPath, filePath, request
c.Log(logger.LevelInfo, "denying file write due to quota limits")
return nil, common.ErrQuotaExceeded
}
err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, fileSize, os.O_TRUNC)
_, err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, fileSize, os.O_TRUNC)
if err != nil {
c.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
return nil, c.GetPermissionDeniedError()

View file

@ -93,7 +93,7 @@ func (c *Connection) Fileread(request *sftp.Request) (io.ReaderAt, error) {
return nil, err
}
if err := common.ExecutePreAction(c.BaseConnection, common.OperationPreDownload, p, request.Filepath, 0, 0); err != nil {
if _, err := common.ExecutePreAction(c.BaseConnection, common.OperationPreDownload, p, request.Filepath, 0, 0); err != nil {
c.Log(logger.LevelDebug, "download for file %#v denied by pre action: %v", request.Filepath, err)
return nil, c.GetPermissionDeniedError()
}
@ -401,7 +401,7 @@ func (c *Connection) handleSFTPUploadToNewFile(fs vfs.Fs, pflags sftp.FileOpenFl
return nil, c.GetQuotaExceededError()
}
if err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, 0, 0); err != nil {
if _, err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, 0, 0); err != nil {
c.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
return nil, c.GetPermissionDeniedError()
}
@ -449,7 +449,7 @@ func (c *Connection) handleSFTPUploadToExistingFile(fs vfs.Fs, pflags sftp.FileO
return nil, err
}
if err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, fileSize, osFlags); err != nil {
if _, err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, fileSize, osFlags); err != nil {
c.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
return nil, c.GetPermissionDeniedError()
}

View file

@ -236,7 +236,7 @@ func (c *scpCommand) handleUploadFile(fs vfs.Fs, resolvedPath, filePath string,
c.sendErrorMessage(nil, err)
return err
}
err := common.ExecutePreAction(c.connection.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath,
_, err := common.ExecutePreAction(c.connection.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath,
fileSize, os.O_TRUNC)
if err != nil {
c.connection.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
@ -532,7 +532,7 @@ func (c *scpCommand) handleDownload(filePath string) error {
return common.ErrPermissionDenied
}
if err := common.ExecutePreAction(c.connection.BaseConnection, common.OperationPreDownload, p, filePath, 0, 0); err != nil {
if _, err := common.ExecutePreAction(c.connection.BaseConnection, common.OperationPreDownload, p, filePath, 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

View file

@ -169,7 +169,7 @@ func (f *webDavFile) checkFirstRead() error {
f.Connection.Log(logger.LevelWarn, "reading file %#v is not allowed", f.GetVirtualPath())
return f.Connection.GetErrorForDeniedFile(policy)
}
err := common.ExecutePreAction(f.Connection, common.OperationPreDownload, f.GetFsPath(), f.GetVirtualPath(), 0, 0)
_, err := common.ExecutePreAction(f.Connection, common.OperationPreDownload, f.GetFsPath(), f.GetVirtualPath(), 0, 0)
if err != nil {
f.Connection.Log(logger.LevelDebug, "download for file %#v denied by pre action: %v", f.GetVirtualPath(), err)
return f.Connection.GetPermissionDeniedError()

View file

@ -211,7 +211,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.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, 0, 0); err != nil {
if _, err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath, 0, 0); err != nil {
c.Log(logger.LevelDebug, "upload for file %#v denied by pre action: %v", requestPath, err)
return nil, c.GetPermissionDeniedError()
}
@ -243,7 +243,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.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath,
if _, err := common.ExecutePreAction(c.BaseConnection, common.OperationPreUpload, resolvedPath, requestPath,
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()

View file

@ -350,7 +350,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<b>Path filters</b>
</div>
<div class="card-body">
<h6 class="card-title mb-4">Shell-like pattern filters for filesystem events. For example "/adir/*.txt"" will match paths in the "/adir" directory ending with ".txt"</h6>
<h6 class="card-title mb-4">Shell-like pattern filters for filesystem events. For example "/adir/*.txt"" will match paths in the "/adir" directory ending with ".txt". Double asterisk is supported, for example "/**/*.txt" will match any file ending with ".txt". "/mydir/**" will match any entry in "/mydir"</h6>
<div class="form-group row">
<div class="col-md-12 form_field_fs_paths_outer">
{{range $idx, $val := .Rule.Conditions.Options.FsPaths}}