mirror of
https://github.com/drakkan/sftpgo.git
synced 2025-12-06 14:20:55 +03:00
command hooks: allow to pass custom arguments
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
@@ -20,6 +20,8 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/internal/util"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -28,8 +30,25 @@ const (
|
||||
defaultTimeout = 30
|
||||
)
|
||||
|
||||
// Supported hook names
|
||||
const (
|
||||
HookFsActions = "fs_actions"
|
||||
HookProviderActions = "provider_actions"
|
||||
HookStartup = "startup"
|
||||
HookPostConnect = "post_connect"
|
||||
HookPostDisconnect = "post_disconnect"
|
||||
HookDataRetention = "data_retention"
|
||||
HookCheckPassword = "check_password"
|
||||
HookPreLogin = "pre_login"
|
||||
HookPostLogin = "post_login"
|
||||
HookExternalAuth = "external_auth"
|
||||
HookKeyboardInteractive = "keyboard_interactive"
|
||||
)
|
||||
|
||||
var (
|
||||
config Config
|
||||
config Config
|
||||
supportedHooks = []string{HookFsActions, HookProviderActions, HookStartup, HookPostConnect, HookPostDisconnect,
|
||||
HookDataRetention, HookCheckPassword, HookPreLogin, HookPostLogin, HookExternalAuth, HookKeyboardInteractive}
|
||||
)
|
||||
|
||||
// Command define the configuration for a specific commands
|
||||
@@ -41,10 +60,14 @@ type Command struct {
|
||||
// Do not use variables with the SFTPGO_ prefix to avoid conflicts with env
|
||||
// vars that SFTPGo sets
|
||||
Timeout int `json:"timeout" mapstructure:"timeout"`
|
||||
// Env defines additional environment variable for the commands.
|
||||
// Env defines additional environment variable for the command.
|
||||
// Each entry is of the form "key=value".
|
||||
// These values are added to the global environment variables if any
|
||||
Env []string `json:"env" mapstructure:"env"`
|
||||
// Args defines arguments to pass to the specified command
|
||||
Args []string `json:"args" mapstructure:"args"`
|
||||
// if not empty both command path and hook name must match
|
||||
Hook string `json:"hook" mapstructure:"hook"`
|
||||
}
|
||||
|
||||
// Config defines the configuration for external commands such as
|
||||
@@ -93,23 +116,33 @@ func (c Config) Initialize() error {
|
||||
return fmt.Errorf("invalid env var %#v for command %#v", env, cmd.Path)
|
||||
}
|
||||
}
|
||||
// don't validate args, we allow to pass empty arguments
|
||||
if cmd.Hook != "" {
|
||||
if !util.Contains(supportedHooks, cmd.Hook) {
|
||||
return fmt.Errorf("invalid hook name %q, supported values: %+v", cmd.Hook, supportedHooks)
|
||||
}
|
||||
}
|
||||
}
|
||||
config = c
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetConfig returns the configuration for the specified command
|
||||
func GetConfig(command string) (time.Duration, []string) {
|
||||
func GetConfig(command, hook string) (time.Duration, []string, []string) {
|
||||
env := os.Environ()
|
||||
var args []string
|
||||
timeout := time.Duration(config.Timeout) * time.Second
|
||||
env = append(env, config.Env...)
|
||||
for _, cmd := range config.Commands {
|
||||
if cmd.Path == command {
|
||||
timeout = time.Duration(cmd.Timeout) * time.Second
|
||||
env = append(env, cmd.Env...)
|
||||
break
|
||||
if cmd.Hook == "" || cmd.Hook == hook {
|
||||
timeout = time.Duration(cmd.Timeout) * time.Second
|
||||
env = append(env, cmd.Env...)
|
||||
args = cmd.Args
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return timeout, env
|
||||
return timeout, env, args
|
||||
}
|
||||
|
||||
@@ -33,15 +33,17 @@ func TestCommandConfig(t *testing.T) {
|
||||
assert.Equal(t, cfg.Timeout, config.Timeout)
|
||||
assert.Equal(t, cfg.Env, config.Env)
|
||||
assert.Len(t, cfg.Commands, 0)
|
||||
timeout, env := GetConfig("cmd")
|
||||
timeout, env, args := GetConfig("cmd", "")
|
||||
assert.Equal(t, time.Duration(config.Timeout)*time.Second, timeout)
|
||||
assert.Contains(t, env, "a=b")
|
||||
assert.Len(t, args, 0)
|
||||
|
||||
cfg.Commands = []Command{
|
||||
{
|
||||
Path: "cmd1",
|
||||
Timeout: 30,
|
||||
Env: []string{"c=d"},
|
||||
Args: []string{"1", "", "2"},
|
||||
},
|
||||
{
|
||||
Path: "cmd2",
|
||||
@@ -57,20 +59,68 @@ func TestCommandConfig(t *testing.T) {
|
||||
assert.Equal(t, cfg.Commands[0].Path, config.Commands[0].Path)
|
||||
assert.Equal(t, cfg.Commands[0].Timeout, config.Commands[0].Timeout)
|
||||
assert.Equal(t, cfg.Commands[0].Env, config.Commands[0].Env)
|
||||
assert.Equal(t, cfg.Commands[0].Args, config.Commands[0].Args)
|
||||
assert.Equal(t, cfg.Commands[1].Path, config.Commands[1].Path)
|
||||
assert.Equal(t, cfg.Timeout, config.Commands[1].Timeout)
|
||||
assert.Equal(t, cfg.Commands[1].Env, config.Commands[1].Env)
|
||||
assert.Equal(t, cfg.Commands[1].Args, config.Commands[1].Args)
|
||||
}
|
||||
timeout, env = GetConfig("cmd1")
|
||||
timeout, env, args = GetConfig("cmd1", "")
|
||||
assert.Equal(t, time.Duration(config.Commands[0].Timeout)*time.Second, timeout)
|
||||
assert.Contains(t, env, "a=b")
|
||||
assert.Contains(t, env, "c=d")
|
||||
assert.NotContains(t, env, "e=f")
|
||||
timeout, env = GetConfig("cmd2")
|
||||
if assert.Len(t, args, 3) {
|
||||
assert.Equal(t, "1", args[0])
|
||||
assert.Empty(t, args[1])
|
||||
assert.Equal(t, "2", args[2])
|
||||
}
|
||||
timeout, env, args = GetConfig("cmd2", "")
|
||||
assert.Equal(t, time.Duration(config.Timeout)*time.Second, timeout)
|
||||
assert.Contains(t, env, "a=b")
|
||||
assert.NotContains(t, env, "c=d")
|
||||
assert.Contains(t, env, "e=f")
|
||||
assert.Len(t, args, 0)
|
||||
|
||||
cfg.Commands = []Command{
|
||||
{
|
||||
Path: "cmd1",
|
||||
Timeout: 30,
|
||||
Env: []string{"c=d"},
|
||||
Args: []string{"1", "", "2"},
|
||||
Hook: HookCheckPassword,
|
||||
},
|
||||
{
|
||||
Path: "cmd1",
|
||||
Timeout: 0,
|
||||
Env: []string{"e=f"},
|
||||
Hook: HookExternalAuth,
|
||||
},
|
||||
}
|
||||
err = cfg.Initialize()
|
||||
require.NoError(t, err)
|
||||
timeout, env, args = GetConfig("cmd1", "")
|
||||
assert.Equal(t, time.Duration(config.Timeout)*time.Second, timeout)
|
||||
assert.Contains(t, env, "a=b")
|
||||
assert.NotContains(t, env, "c=d")
|
||||
assert.NotContains(t, env, "e=f")
|
||||
assert.Len(t, args, 0)
|
||||
timeout, env, args = GetConfig("cmd1", HookCheckPassword)
|
||||
assert.Equal(t, time.Duration(config.Commands[0].Timeout)*time.Second, timeout)
|
||||
assert.Contains(t, env, "a=b")
|
||||
assert.Contains(t, env, "c=d")
|
||||
assert.NotContains(t, env, "e=f")
|
||||
if assert.Len(t, args, 3) {
|
||||
assert.Equal(t, "1", args[0])
|
||||
assert.Empty(t, args[1])
|
||||
assert.Equal(t, "2", args[2])
|
||||
}
|
||||
timeout, env, args = GetConfig("cmd1", HookExternalAuth)
|
||||
assert.Equal(t, time.Duration(cfg.Timeout)*time.Second, timeout)
|
||||
assert.Contains(t, env, "a=b")
|
||||
assert.NotContains(t, env, "c=d")
|
||||
assert.Contains(t, env, "e=f")
|
||||
assert.Len(t, args, 0)
|
||||
}
|
||||
|
||||
func TestConfigErrors(t *testing.T) {
|
||||
@@ -116,4 +166,16 @@ func TestConfigErrors(t *testing.T) {
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "invalid env var")
|
||||
}
|
||||
c.Commands = []Command{
|
||||
{
|
||||
Path: "path",
|
||||
Timeout: 30,
|
||||
Env: []string{"a=b"},
|
||||
Hook: "invali",
|
||||
},
|
||||
}
|
||||
err = c.Initialize()
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "invalid hook name")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user