replace utils.Contains with slices.Contains
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
parent
bd5eb03d9c
commit
d94f80c8da
51 changed files with 353 additions and 322 deletions
|
@ -30,6 +30,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -249,7 +250,7 @@ func (c *Configuration) Initialize(configDir string) error {
|
||||||
if c.RenewDays < 1 {
|
if c.RenewDays < 1 {
|
||||||
return fmt.Errorf("invalid number of days remaining before renewal: %d", c.RenewDays)
|
return fmt.Errorf("invalid number of days remaining before renewal: %d", c.RenewDays)
|
||||||
}
|
}
|
||||||
if !util.Contains(supportedKeyTypes, c.KeyType) {
|
if !slices.Contains(supportedKeyTypes, c.KeyType) {
|
||||||
return fmt.Errorf("invalid key type %q", c.KeyType)
|
return fmt.Errorf("invalid key type %q", c.KeyType)
|
||||||
}
|
}
|
||||||
caURL, err := url.Parse(c.CAEndpoint)
|
caURL, err := url.Parse(c.CAEndpoint)
|
||||||
|
|
|
@ -17,10 +17,9 @@ package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/v2/internal/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -117,7 +116,7 @@ func (c Config) Initialize() error {
|
||||||
}
|
}
|
||||||
// don't validate args, we allow to pass empty arguments
|
// don't validate args, we allow to pass empty arguments
|
||||||
if cmd.Hook != "" {
|
if cmd.Hook != "" {
|
||||||
if !util.Contains(supportedHooks, cmd.Hook) {
|
if !slices.Contains(supportedHooks, cmd.Hook) {
|
||||||
return fmt.Errorf("invalid hook name %q, supported values: %+v", cmd.Hook, supportedHooks)
|
return fmt.Errorf("invalid hook name %q, supported values: %+v", cmd.Hook, supportedHooks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
@ -86,7 +87,7 @@ func InitializeActionHandler(handler ActionHandler) {
|
||||||
func ExecutePreAction(conn *BaseConnection, operation, filePath, virtualPath string, fileSize int64, openFlags int) (int, error) {
|
func ExecutePreAction(conn *BaseConnection, operation, filePath, virtualPath string, fileSize int64, openFlags int) (int, error) {
|
||||||
var event *notifier.FsEvent
|
var event *notifier.FsEvent
|
||||||
hasNotifiersPlugin := plugin.Handler.HasNotifiers()
|
hasNotifiersPlugin := plugin.Handler.HasNotifiers()
|
||||||
hasHook := util.Contains(Config.Actions.ExecuteOn, operation)
|
hasHook := slices.Contains(Config.Actions.ExecuteOn, operation)
|
||||||
hasRules := eventManager.hasFsRules()
|
hasRules := eventManager.hasFsRules()
|
||||||
if !hasHook && !hasNotifiersPlugin && !hasRules {
|
if !hasHook && !hasNotifiersPlugin && !hasRules {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
|
@ -132,7 +133,7 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua
|
||||||
fileSize int64, err error, elapsed int64, metadata map[string]string,
|
fileSize int64, err error, elapsed int64, metadata map[string]string,
|
||||||
) error {
|
) error {
|
||||||
hasNotifiersPlugin := plugin.Handler.HasNotifiers()
|
hasNotifiersPlugin := plugin.Handler.HasNotifiers()
|
||||||
hasHook := util.Contains(Config.Actions.ExecuteOn, operation)
|
hasHook := slices.Contains(Config.Actions.ExecuteOn, operation)
|
||||||
hasRules := eventManager.hasFsRules()
|
hasRules := eventManager.hasFsRules()
|
||||||
if !hasHook && !hasNotifiersPlugin && !hasRules {
|
if !hasHook && !hasNotifiersPlugin && !hasRules {
|
||||||
return nil
|
return nil
|
||||||
|
@ -173,7 +174,7 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if hasHook {
|
if hasHook {
|
||||||
if util.Contains(Config.Actions.ExecuteSync, operation) {
|
if slices.Contains(Config.Actions.ExecuteSync, operation) {
|
||||||
_, err := actionHandler.Handle(notification)
|
_, err := actionHandler.Handle(notification)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -247,7 +248,7 @@ func newActionNotification(
|
||||||
type defaultActionHandler struct{}
|
type defaultActionHandler struct{}
|
||||||
|
|
||||||
func (h *defaultActionHandler) Handle(event *notifier.FsEvent) (int, error) {
|
func (h *defaultActionHandler) Handle(event *notifier.FsEvent) (int, error) {
|
||||||
if !util.Contains(Config.Actions.ExecuteOn, event.Action) {
|
if !slices.Contains(Config.Actions.ExecuteOn, event.Action) {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -207,7 +208,7 @@ func Initialize(c Configuration, isShared int) error {
|
||||||
Config.rateLimitersList = rateLimitersList
|
Config.rateLimitersList = rateLimitersList
|
||||||
}
|
}
|
||||||
if c.DefenderConfig.Enabled {
|
if c.DefenderConfig.Enabled {
|
||||||
if !util.Contains(supportedDefenderDrivers, c.DefenderConfig.Driver) {
|
if !slices.Contains(supportedDefenderDrivers, c.DefenderConfig.Driver) {
|
||||||
return fmt.Errorf("unsupported defender driver %q", c.DefenderConfig.Driver)
|
return fmt.Errorf("unsupported defender driver %q", c.DefenderConfig.Driver)
|
||||||
}
|
}
|
||||||
var defender Defender
|
var defender Defender
|
||||||
|
@ -777,7 +778,7 @@ func (c *Configuration) checkPostDisconnectHook(remoteAddr, protocol, username,
|
||||||
if c.PostDisconnectHook == "" {
|
if c.PostDisconnectHook == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !util.Contains(disconnHookProtocols, protocol) {
|
if !slices.Contains(disconnHookProtocols, protocol) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go c.executePostDisconnectHook(remoteAddr, protocol, username, connID, connectionTime)
|
go c.executePostDisconnectHook(remoteAddr, protocol, username, connID, connectionTime)
|
||||||
|
@ -1019,7 +1020,7 @@ func (conns *ActiveConnections) Remove(connectionID string) {
|
||||||
metric.UpdateActiveConnectionsSize(lastIdx)
|
metric.UpdateActiveConnectionsSize(lastIdx)
|
||||||
logger.Debug(conn.GetProtocol(), conn.GetID(), "connection removed, local address %q, remote address %q close fs error: %v, num open connections: %d",
|
logger.Debug(conn.GetProtocol(), conn.GetID(), "connection removed, local address %q, remote address %q close fs error: %v, num open connections: %d",
|
||||||
conn.GetLocalAddress(), conn.GetRemoteAddress(), err, lastIdx)
|
conn.GetLocalAddress(), conn.GetRemoteAddress(), err, lastIdx)
|
||||||
if conn.GetProtocol() == ProtocolFTP && conn.GetUsername() == "" && !util.Contains(ftpLoginCommands, conn.GetCommand()) {
|
if conn.GetProtocol() == ProtocolFTP && conn.GetUsername() == "" && !slices.Contains(ftpLoginCommands, conn.GetCommand()) {
|
||||||
ip := util.GetIPFromRemoteAddress(conn.GetRemoteAddress())
|
ip := util.GetIPFromRemoteAddress(conn.GetRemoteAddress())
|
||||||
logger.ConnectionFailedLog("", ip, dataprovider.LoginMethodNoAuthTried, ProtocolFTP,
|
logger.ConnectionFailedLog("", ip, dataprovider.LoginMethodNoAuthTried, ProtocolFTP,
|
||||||
dataprovider.ErrNoAuthTried.Error())
|
dataprovider.ErrNoAuthTried.Error())
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -1226,8 +1227,8 @@ func TestFolderCopy(t *testing.T) {
|
||||||
folder.ID = 2
|
folder.ID = 2
|
||||||
folder.Users = []string{"user3"}
|
folder.Users = []string{"user3"}
|
||||||
require.Len(t, folderCopy.Users, 2)
|
require.Len(t, folderCopy.Users, 2)
|
||||||
require.True(t, util.Contains(folderCopy.Users, "user1"))
|
require.True(t, slices.Contains(folderCopy.Users, "user1"))
|
||||||
require.True(t, util.Contains(folderCopy.Users, "user2"))
|
require.True(t, slices.Contains(folderCopy.Users, "user2"))
|
||||||
require.Equal(t, int64(1), folderCopy.ID)
|
require.Equal(t, int64(1), folderCopy.ID)
|
||||||
require.Equal(t, folder.Name, folderCopy.Name)
|
require.Equal(t, folder.Name, folderCopy.Name)
|
||||||
require.Equal(t, folder.MappedPath, folderCopy.MappedPath)
|
require.Equal(t, folder.MappedPath, folderCopy.MappedPath)
|
||||||
|
@ -1243,7 +1244,7 @@ func TestFolderCopy(t *testing.T) {
|
||||||
folderCopy = folder.GetACopy()
|
folderCopy = folder.GetACopy()
|
||||||
folder.FsConfig.CryptConfig.Passphrase = kms.NewEmptySecret()
|
folder.FsConfig.CryptConfig.Passphrase = kms.NewEmptySecret()
|
||||||
require.Len(t, folderCopy.Users, 1)
|
require.Len(t, folderCopy.Users, 1)
|
||||||
require.True(t, util.Contains(folderCopy.Users, "user3"))
|
require.True(t, slices.Contains(folderCopy.Users, "user3"))
|
||||||
require.Equal(t, int64(2), folderCopy.ID)
|
require.Equal(t, int64(2), folderCopy.ID)
|
||||||
require.Equal(t, folder.Name, folderCopy.Name)
|
require.Equal(t, folder.Name, folderCopy.Name)
|
||||||
require.Equal(t, folder.MappedPath, folderCopy.MappedPath)
|
require.Equal(t, folder.MappedPath, folderCopy.MappedPath)
|
||||||
|
|
|
@ -63,7 +63,7 @@ type BaseConnection struct {
|
||||||
// NewBaseConnection returns a new BaseConnection
|
// NewBaseConnection returns a new BaseConnection
|
||||||
func NewBaseConnection(id, protocol, localAddr, remoteAddr string, user dataprovider.User) *BaseConnection {
|
func NewBaseConnection(id, protocol, localAddr, remoteAddr string, user dataprovider.User) *BaseConnection {
|
||||||
connID := id
|
connID := id
|
||||||
if util.Contains(supportedProtocols, protocol) {
|
if slices.Contains(supportedProtocols, protocol) {
|
||||||
connID = fmt.Sprintf("%s_%s", protocol, id)
|
connID = fmt.Sprintf("%s_%s", protocol, id)
|
||||||
}
|
}
|
||||||
user.UploadBandwidth, user.DownloadBandwidth = user.GetBandwidthForIP(util.GetIPFromRemoteAddress(remoteAddr), connID)
|
user.UploadBandwidth, user.DownloadBandwidth = user.GetBandwidthForIP(util.GetIPFromRemoteAddress(remoteAddr), connID)
|
||||||
|
@ -132,7 +132,7 @@ func (c *BaseConnection) GetRemoteIP() string {
|
||||||
// SetProtocol sets the protocol for this connection
|
// SetProtocol sets the protocol for this connection
|
||||||
func (c *BaseConnection) SetProtocol(protocol string) {
|
func (c *BaseConnection) SetProtocol(protocol string) {
|
||||||
c.protocol = protocol
|
c.protocol = protocol
|
||||||
if util.Contains(supportedProtocols, c.protocol) {
|
if slices.Contains(supportedProtocols, c.protocol) {
|
||||||
c.ID = fmt.Sprintf("%v_%v", c.protocol, c.ID)
|
c.ID = fmt.Sprintf("%v_%v", c.protocol, c.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -389,7 +390,7 @@ func TestErrorsMapping(t *testing.T) {
|
||||||
err := conn.GetFsError(fs, os.ErrNotExist)
|
err := conn.GetFsError(fs, os.ErrNotExist)
|
||||||
if protocol == ProtocolSFTP {
|
if protocol == ProtocolSFTP {
|
||||||
assert.ErrorIs(t, err, sftp.ErrSSHFxNoSuchFile)
|
assert.ErrorIs(t, err, sftp.ErrSSHFxNoSuchFile)
|
||||||
} else if util.Contains(osErrorsProtocols, protocol) {
|
} else if slices.Contains(osErrorsProtocols, protocol) {
|
||||||
assert.EqualError(t, err, os.ErrNotExist.Error())
|
assert.EqualError(t, err, os.ErrNotExist.Error())
|
||||||
} else {
|
} else {
|
||||||
assert.EqualError(t, err, ErrNotExist.Error())
|
assert.EqualError(t, err, ErrNotExist.Error())
|
||||||
|
|
|
@ -31,6 +31,7 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -307,7 +308,7 @@ func (*eventRulesContainer) checkIPDLoginEventMatch(conditions *dataprovider.Eve
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*eventRulesContainer) checkProviderEventMatch(conditions *dataprovider.EventConditions, params *EventParams) bool {
|
func (*eventRulesContainer) checkProviderEventMatch(conditions *dataprovider.EventConditions, params *EventParams) bool {
|
||||||
if !util.Contains(conditions.ProviderEvents, params.Event) {
|
if !slices.Contains(conditions.ProviderEvents, params.Event) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !checkEventConditionPatterns(params.Name, conditions.Options.Names) {
|
if !checkEventConditionPatterns(params.Name, conditions.Options.Names) {
|
||||||
|
@ -316,14 +317,14 @@ func (*eventRulesContainer) checkProviderEventMatch(conditions *dataprovider.Eve
|
||||||
if !checkEventConditionPatterns(params.Role, conditions.Options.RoleNames) {
|
if !checkEventConditionPatterns(params.Role, conditions.Options.RoleNames) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if len(conditions.Options.ProviderObjects) > 0 && !util.Contains(conditions.Options.ProviderObjects, params.ObjectType) {
|
if len(conditions.Options.ProviderObjects) > 0 && !slices.Contains(conditions.Options.ProviderObjects, params.ObjectType) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*eventRulesContainer) checkFsEventMatch(conditions *dataprovider.EventConditions, params *EventParams) bool {
|
func (*eventRulesContainer) checkFsEventMatch(conditions *dataprovider.EventConditions, params *EventParams) bool {
|
||||||
if !util.Contains(conditions.FsEvents, params.Event) {
|
if !slices.Contains(conditions.FsEvents, params.Event) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !checkEventConditionPatterns(params.Name, conditions.Options.Names) {
|
if !checkEventConditionPatterns(params.Name, conditions.Options.Names) {
|
||||||
|
@ -338,7 +339,7 @@ func (*eventRulesContainer) checkFsEventMatch(conditions *dataprovider.EventCond
|
||||||
if !checkEventConditionPatterns(params.VirtualPath, conditions.Options.FsPaths) {
|
if !checkEventConditionPatterns(params.VirtualPath, conditions.Options.FsPaths) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if len(conditions.Options.Protocols) > 0 && !util.Contains(conditions.Options.Protocols, params.Protocol) {
|
if len(conditions.Options.Protocols) > 0 && !slices.Contains(conditions.Options.Protocols, params.Protocol) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if params.Event == operationUpload || params.Event == operationDownload {
|
if params.Event == operationUpload || params.Event == operationDownload {
|
||||||
|
|
|
@ -30,6 +30,7 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -3978,9 +3979,9 @@ func TestEventRule(t *testing.T) {
|
||||||
}, 3000*time.Millisecond, 100*time.Millisecond)
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
||||||
email := lastReceivedEmail.get()
|
email := lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 3)
|
assert.Len(t, email.To, 3)
|
||||||
assert.True(t, util.Contains(email.To, "test1@example.com"))
|
assert.True(t, slices.Contains(email.To, "test1@example.com"))
|
||||||
assert.True(t, util.Contains(email.To, "test2@example.com"))
|
assert.True(t, slices.Contains(email.To, "test2@example.com"))
|
||||||
assert.True(t, util.Contains(email.To, "test3@example.com"))
|
assert.True(t, slices.Contains(email.To, "test3@example.com"))
|
||||||
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: New "upload" from "%s" status OK`, user.Username))
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: New "upload" from "%s" status OK`, user.Username))
|
||||||
// test the failure action, we download a file that exceeds the transfer quota limit
|
// test the failure action, we download a file that exceeds the transfer quota limit
|
||||||
err = writeSFTPFileNoCheck(path.Join("subdir1", testFileName), 1*1024*1024+65535, client)
|
err = writeSFTPFileNoCheck(path.Join("subdir1", testFileName), 1*1024*1024+65535, client)
|
||||||
|
@ -3999,9 +4000,9 @@ func TestEventRule(t *testing.T) {
|
||||||
}, 3000*time.Millisecond, 100*time.Millisecond)
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
||||||
email = lastReceivedEmail.get()
|
email = lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 3)
|
assert.Len(t, email.To, 3)
|
||||||
assert.True(t, util.Contains(email.To, "test1@example.com"))
|
assert.True(t, slices.Contains(email.To, "test1@example.com"))
|
||||||
assert.True(t, util.Contains(email.To, "test2@example.com"))
|
assert.True(t, slices.Contains(email.To, "test2@example.com"))
|
||||||
assert.True(t, util.Contains(email.To, "test3@example.com"))
|
assert.True(t, slices.Contains(email.To, "test3@example.com"))
|
||||||
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: New "download" from "%s" status KO`, user.Username))
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: New "download" from "%s" status KO`, user.Username))
|
||||||
assert.Contains(t, email.Data, `"download" failed`)
|
assert.Contains(t, email.Data, `"download" failed`)
|
||||||
assert.Contains(t, email.Data, common.ErrReadQuotaExceeded.Error())
|
assert.Contains(t, email.Data, common.ErrReadQuotaExceeded.Error())
|
||||||
|
@ -4019,7 +4020,7 @@ func TestEventRule(t *testing.T) {
|
||||||
}, 3000*time.Millisecond, 100*time.Millisecond)
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
||||||
email = lastReceivedEmail.get()
|
email = lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 1)
|
assert.Len(t, email.To, 1)
|
||||||
assert.True(t, util.Contains(email.To, "failure@example.com"))
|
assert.True(t, slices.Contains(email.To, "failure@example.com"))
|
||||||
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: Failed "upload" from "%s"`, user.Username))
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: Failed "upload" from "%s"`, user.Username))
|
||||||
assert.Contains(t, email.Data, fmt.Sprintf(`action %q failed`, action1.Name))
|
assert.Contains(t, email.Data, fmt.Sprintf(`action %q failed`, action1.Name))
|
||||||
// now test the download rule
|
// now test the download rule
|
||||||
|
@ -4036,9 +4037,9 @@ func TestEventRule(t *testing.T) {
|
||||||
}, 3000*time.Millisecond, 100*time.Millisecond)
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
||||||
email = lastReceivedEmail.get()
|
email = lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 3)
|
assert.Len(t, email.To, 3)
|
||||||
assert.True(t, util.Contains(email.To, "test1@example.com"))
|
assert.True(t, slices.Contains(email.To, "test1@example.com"))
|
||||||
assert.True(t, util.Contains(email.To, "test2@example.com"))
|
assert.True(t, slices.Contains(email.To, "test2@example.com"))
|
||||||
assert.True(t, util.Contains(email.To, "test3@example.com"))
|
assert.True(t, slices.Contains(email.To, "test3@example.com"))
|
||||||
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: New "download" from "%s"`, user.Username))
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: New "download" from "%s"`, user.Username))
|
||||||
}
|
}
|
||||||
// test upload action command with arguments
|
// test upload action command with arguments
|
||||||
|
@ -4079,9 +4080,9 @@ func TestEventRule(t *testing.T) {
|
||||||
}, 3000*time.Millisecond, 100*time.Millisecond)
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
||||||
email := lastReceivedEmail.get()
|
email := lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 3)
|
assert.Len(t, email.To, 3)
|
||||||
assert.True(t, util.Contains(email.To, "test1@example.com"))
|
assert.True(t, slices.Contains(email.To, "test1@example.com"))
|
||||||
assert.True(t, util.Contains(email.To, "test2@example.com"))
|
assert.True(t, slices.Contains(email.To, "test2@example.com"))
|
||||||
assert.True(t, util.Contains(email.To, "test3@example.com"))
|
assert.True(t, slices.Contains(email.To, "test3@example.com"))
|
||||||
assert.Contains(t, email.Data, `Subject: New "delete" from "admin"`)
|
assert.Contains(t, email.Data, `Subject: New "delete" from "admin"`)
|
||||||
_, err = httpdtest.RemoveEventRule(rule3, http.StatusOK)
|
_, err = httpdtest.RemoveEventRule(rule3, http.StatusOK)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -4236,7 +4237,7 @@ func TestEventRuleProviderEvents(t *testing.T) {
|
||||||
}, 3000*time.Millisecond, 100*time.Millisecond)
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
||||||
email := lastReceivedEmail.get()
|
email := lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 1)
|
assert.Len(t, email.To, 1)
|
||||||
assert.True(t, util.Contains(email.To, "test3@example.com"))
|
assert.True(t, slices.Contains(email.To, "test3@example.com"))
|
||||||
assert.Contains(t, email.Data, `Subject: New "update" from "admin"`)
|
assert.Contains(t, email.Data, `Subject: New "update" from "admin"`)
|
||||||
}
|
}
|
||||||
// now delete the script to generate an error
|
// now delete the script to generate an error
|
||||||
|
@ -4251,7 +4252,7 @@ func TestEventRuleProviderEvents(t *testing.T) {
|
||||||
}, 3000*time.Millisecond, 100*time.Millisecond)
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
||||||
email := lastReceivedEmail.get()
|
email := lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 1)
|
assert.Len(t, email.To, 1)
|
||||||
assert.True(t, util.Contains(email.To, "failure@example.com"))
|
assert.True(t, slices.Contains(email.To, "failure@example.com"))
|
||||||
assert.Contains(t, email.Data, `Subject: Failed "update" from "admin"`)
|
assert.Contains(t, email.Data, `Subject: Failed "update" from "admin"`)
|
||||||
assert.Contains(t, email.Data, fmt.Sprintf("Object name: %s object type: folder", folder.Name))
|
assert.Contains(t, email.Data, fmt.Sprintf("Object name: %s object type: folder", folder.Name))
|
||||||
lastReceivedEmail.reset()
|
lastReceivedEmail.reset()
|
||||||
|
@ -5306,7 +5307,7 @@ func TestBackupAsAttachment(t *testing.T) {
|
||||||
}, 3000*time.Millisecond, 100*time.Millisecond)
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
||||||
email := lastReceivedEmail.get()
|
email := lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 1)
|
assert.Len(t, email.To, 1)
|
||||||
assert.True(t, util.Contains(email.To, "test@example.com"))
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
||||||
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s OK"`, renewalEvent))
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s OK"`, renewalEvent))
|
||||||
assert.Contains(t, email.Data, `Domain: example.com`)
|
assert.Contains(t, email.Data, `Domain: example.com`)
|
||||||
assert.Contains(t, email.Data, "Content-Type: application/json")
|
assert.Contains(t, email.Data, "Content-Type: application/json")
|
||||||
|
@ -5676,7 +5677,7 @@ func TestEventActionCompressQuotaErrors(t *testing.T) {
|
||||||
}, 3*time.Second, 100*time.Millisecond)
|
}, 3*time.Second, 100*time.Millisecond)
|
||||||
email := lastReceivedEmail.get()
|
email := lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 1)
|
assert.Len(t, email.To, 1)
|
||||||
assert.True(t, util.Contains(email.To, "test@example.com"))
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
||||||
assert.Contains(t, email.Data, `Subject: "Compress failed"`)
|
assert.Contains(t, email.Data, `Subject: "Compress failed"`)
|
||||||
assert.Contains(t, email.Data, common.ErrQuotaExceeded.Error())
|
assert.Contains(t, email.Data, common.ErrQuotaExceeded.Error())
|
||||||
// update quota size so the user is already overquota
|
// update quota size so the user is already overquota
|
||||||
|
@ -5691,7 +5692,7 @@ func TestEventActionCompressQuotaErrors(t *testing.T) {
|
||||||
}, 3*time.Second, 100*time.Millisecond)
|
}, 3*time.Second, 100*time.Millisecond)
|
||||||
email = lastReceivedEmail.get()
|
email = lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 1)
|
assert.Len(t, email.To, 1)
|
||||||
assert.True(t, util.Contains(email.To, "test@example.com"))
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
||||||
assert.Contains(t, email.Data, `Subject: "Compress failed"`)
|
assert.Contains(t, email.Data, `Subject: "Compress failed"`)
|
||||||
assert.Contains(t, email.Data, common.ErrQuotaExceeded.Error())
|
assert.Contains(t, email.Data, common.ErrQuotaExceeded.Error())
|
||||||
// remove the path to compress to trigger an error for size estimation
|
// remove the path to compress to trigger an error for size estimation
|
||||||
|
@ -5705,7 +5706,7 @@ func TestEventActionCompressQuotaErrors(t *testing.T) {
|
||||||
}, 3*time.Second, 100*time.Millisecond)
|
}, 3*time.Second, 100*time.Millisecond)
|
||||||
email = lastReceivedEmail.get()
|
email = lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 1)
|
assert.Len(t, email.To, 1)
|
||||||
assert.True(t, util.Contains(email.To, "test@example.com"))
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
||||||
assert.Contains(t, email.Data, `Subject: "Compress failed"`)
|
assert.Contains(t, email.Data, `Subject: "Compress failed"`)
|
||||||
assert.Contains(t, email.Data, "unable to estimate archive size")
|
assert.Contains(t, email.Data, "unable to estimate archive size")
|
||||||
}
|
}
|
||||||
|
@ -6041,7 +6042,7 @@ func TestEventActionEmailAttachments(t *testing.T) {
|
||||||
}, 1500*time.Millisecond, 100*time.Millisecond)
|
}, 1500*time.Millisecond, 100*time.Millisecond)
|
||||||
email := lastReceivedEmail.get()
|
email := lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 1)
|
assert.Len(t, email.To, 1)
|
||||||
assert.True(t, util.Contains(email.To, "test@example.com"))
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
||||||
assert.Contains(t, email.Data, `Subject: "upload" from`)
|
assert.Contains(t, email.Data, `Subject: "upload" from`)
|
||||||
assert.Contains(t, email.Data, "Content-Disposition: attachment")
|
assert.Contains(t, email.Data, "Content-Disposition: attachment")
|
||||||
}
|
}
|
||||||
|
@ -6218,7 +6219,7 @@ func TestEventActionsRetentionReports(t *testing.T) {
|
||||||
|
|
||||||
email := lastReceivedEmail.get()
|
email := lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 1)
|
assert.Len(t, email.To, 1)
|
||||||
assert.True(t, util.Contains(email.To, "test@example.com"))
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
||||||
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "upload" from "%s"`, user.Username))
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "upload" from "%s"`, user.Username))
|
||||||
assert.Contains(t, email.Data, "Content-Disposition: attachment")
|
assert.Contains(t, email.Data, "Content-Disposition: attachment")
|
||||||
_, err = client.Stat(testDir)
|
_, err = client.Stat(testDir)
|
||||||
|
@ -6391,7 +6392,7 @@ func TestEventRuleFirstUploadDownloadActions(t *testing.T) {
|
||||||
}, 1500*time.Millisecond, 100*time.Millisecond)
|
}, 1500*time.Millisecond, 100*time.Millisecond)
|
||||||
email := lastReceivedEmail.get()
|
email := lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 1)
|
assert.Len(t, email.To, 1)
|
||||||
assert.True(t, util.Contains(email.To, "test@example.com"))
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
||||||
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "first-upload" from "%s"`, user.Username))
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "first-upload" from "%s"`, user.Username))
|
||||||
lastReceivedEmail.reset()
|
lastReceivedEmail.reset()
|
||||||
// a new upload will not produce a new notification
|
// a new upload will not produce a new notification
|
||||||
|
@ -6414,7 +6415,7 @@ func TestEventRuleFirstUploadDownloadActions(t *testing.T) {
|
||||||
}, 1500*time.Millisecond, 100*time.Millisecond)
|
}, 1500*time.Millisecond, 100*time.Millisecond)
|
||||||
email = lastReceivedEmail.get()
|
email = lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 1)
|
assert.Len(t, email.To, 1)
|
||||||
assert.True(t, util.Contains(email.To, "test@example.com"))
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
||||||
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "first-download" from "%s"`, user.Username))
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "first-download" from "%s"`, user.Username))
|
||||||
// download again
|
// download again
|
||||||
lastReceivedEmail.reset()
|
lastReceivedEmail.reset()
|
||||||
|
@ -6510,7 +6511,7 @@ func TestEventRuleRenameEvent(t *testing.T) {
|
||||||
}, 1500*time.Millisecond, 100*time.Millisecond)
|
}, 1500*time.Millisecond, 100*time.Millisecond)
|
||||||
email := lastReceivedEmail.get()
|
email := lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 1)
|
assert.Len(t, email.To, 1)
|
||||||
assert.True(t, util.Contains(email.To, "test@example.com"))
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
||||||
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "rename" from "%s"`, user.Username))
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "rename" from "%s"`, user.Username))
|
||||||
assert.Contains(t, email.Data, "Content-Type: text/html")
|
assert.Contains(t, email.Data, "Content-Type: text/html")
|
||||||
assert.Contains(t, email.Data, fmt.Sprintf("Target path %q", path.Join("/subdir", testFileName)))
|
assert.Contains(t, email.Data, fmt.Sprintf("Target path %q", path.Join("/subdir", testFileName)))
|
||||||
|
@ -6644,7 +6645,7 @@ func TestEventRuleIDPLogin(t *testing.T) {
|
||||||
}, 3000*time.Millisecond, 100*time.Millisecond)
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
||||||
email := lastReceivedEmail.get()
|
email := lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 1)
|
assert.Len(t, email.To, 1)
|
||||||
assert.True(t, util.Contains(email.To, "test@example.com"))
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
||||||
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s OK"`, common.IDPLoginUser))
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s OK"`, common.IDPLoginUser))
|
||||||
assert.Contains(t, email.Data, username)
|
assert.Contains(t, email.Data, username)
|
||||||
assert.Contains(t, email.Data, custom1)
|
assert.Contains(t, email.Data, custom1)
|
||||||
|
@ -6708,7 +6709,7 @@ func TestEventRuleIDPLogin(t *testing.T) {
|
||||||
}, 3000*time.Millisecond, 100*time.Millisecond)
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
||||||
email = lastReceivedEmail.get()
|
email = lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 1)
|
assert.Len(t, email.To, 1)
|
||||||
assert.True(t, util.Contains(email.To, "test@example.com"))
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
||||||
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s OK"`, common.IDPLoginAdmin))
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s OK"`, common.IDPLoginAdmin))
|
||||||
assert.Contains(t, email.Data, username)
|
assert.Contains(t, email.Data, username)
|
||||||
assert.Contains(t, email.Data, custom1)
|
assert.Contains(t, email.Data, custom1)
|
||||||
|
@ -6900,7 +6901,7 @@ func TestEventRuleEmailField(t *testing.T) {
|
||||||
}, 3000*time.Millisecond, 100*time.Millisecond)
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
||||||
email := lastReceivedEmail.get()
|
email := lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 1)
|
assert.Len(t, email.To, 1)
|
||||||
assert.True(t, util.Contains(email.To, user.Email))
|
assert.True(t, slices.Contains(email.To, user.Email))
|
||||||
assert.Contains(t, email.Data, `Subject: "add" from "admin"`)
|
assert.Contains(t, email.Data, `Subject: "add" from "admin"`)
|
||||||
|
|
||||||
// if we add a user without email the notification will fail
|
// if we add a user without email the notification will fail
|
||||||
|
@ -6914,7 +6915,7 @@ func TestEventRuleEmailField(t *testing.T) {
|
||||||
}, 3000*time.Millisecond, 100*time.Millisecond)
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
||||||
email = lastReceivedEmail.get()
|
email = lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 1)
|
assert.Len(t, email.To, 1)
|
||||||
assert.True(t, util.Contains(email.To, "failure@example.com"))
|
assert.True(t, slices.Contains(email.To, "failure@example.com"))
|
||||||
assert.Contains(t, email.Data, `no recipient addresses set`)
|
assert.Contains(t, email.Data, `no recipient addresses set`)
|
||||||
|
|
||||||
conn, client, err := getSftpClient(user)
|
conn, client, err := getSftpClient(user)
|
||||||
|
@ -6931,7 +6932,7 @@ func TestEventRuleEmailField(t *testing.T) {
|
||||||
}, 3000*time.Millisecond, 100*time.Millisecond)
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
||||||
email := lastReceivedEmail.get()
|
email := lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 1)
|
assert.Len(t, email.To, 1)
|
||||||
assert.True(t, util.Contains(email.To, user.Email))
|
assert.True(t, slices.Contains(email.To, user.Email))
|
||||||
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "mkdir" from "%s"`, user.Username))
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "mkdir" from "%s"`, user.Username))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7038,7 +7039,7 @@ func TestEventRuleCertificate(t *testing.T) {
|
||||||
}, 3000*time.Millisecond, 100*time.Millisecond)
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
||||||
email := lastReceivedEmail.get()
|
email := lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 1)
|
assert.Len(t, email.To, 1)
|
||||||
assert.True(t, util.Contains(email.To, "test@example.com"))
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
||||||
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s OK"`, renewalEvent))
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s OK"`, renewalEvent))
|
||||||
assert.Contains(t, email.Data, "Content-Type: text/plain")
|
assert.Contains(t, email.Data, "Content-Type: text/plain")
|
||||||
assert.Contains(t, email.Data, `Domain: example.com Timestamp`)
|
assert.Contains(t, email.Data, `Domain: example.com Timestamp`)
|
||||||
|
@ -7058,7 +7059,7 @@ func TestEventRuleCertificate(t *testing.T) {
|
||||||
}, 3000*time.Millisecond, 100*time.Millisecond)
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
||||||
email = lastReceivedEmail.get()
|
email = lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 1)
|
assert.Len(t, email.To, 1)
|
||||||
assert.True(t, util.Contains(email.To, "test@example.com"))
|
assert.True(t, slices.Contains(email.To, "test@example.com"))
|
||||||
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s KO"`, renewalEvent))
|
assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s KO"`, renewalEvent))
|
||||||
assert.Contains(t, email.Data, `Domain: example.com Timestamp`)
|
assert.Contains(t, email.Data, `Domain: example.com Timestamp`)
|
||||||
assert.Contains(t, email.Data, errRenew.Error())
|
assert.Contains(t, email.Data, errRenew.Error())
|
||||||
|
@ -7184,8 +7185,8 @@ func TestEventRuleIPBlocked(t *testing.T) {
|
||||||
}, 3000*time.Millisecond, 100*time.Millisecond)
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
||||||
email := lastReceivedEmail.get()
|
email := lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 2)
|
assert.Len(t, email.To, 2)
|
||||||
assert.True(t, util.Contains(email.To, "test3@example.com"))
|
assert.True(t, slices.Contains(email.To, "test3@example.com"))
|
||||||
assert.True(t, util.Contains(email.To, "test4@example.com"))
|
assert.True(t, slices.Contains(email.To, "test4@example.com"))
|
||||||
assert.Contains(t, email.Data, `Subject: New "IP Blocked"`)
|
assert.Contains(t, email.Data, `Subject: New "IP Blocked"`)
|
||||||
|
|
||||||
err = dataprovider.DeleteEventRule(rule1.Name, "", "", "")
|
err = dataprovider.DeleteEventRule(rule1.Name, "", "", "")
|
||||||
|
@ -8357,7 +8358,7 @@ func TestSFTPLoopError(t *testing.T) {
|
||||||
}, 3000*time.Millisecond, 100*time.Millisecond)
|
}, 3000*time.Millisecond, 100*time.Millisecond)
|
||||||
email := lastReceivedEmail.get()
|
email := lastReceivedEmail.get()
|
||||||
assert.Len(t, email.To, 1)
|
assert.Len(t, email.To, 1)
|
||||||
assert.True(t, util.Contains(email.To, "failure@example.com"))
|
assert.True(t, slices.Contains(email.To, "failure@example.com"))
|
||||||
assert.Contains(t, email.Data, `Subject: Failed action`)
|
assert.Contains(t, email.Data, `Subject: Failed action`)
|
||||||
|
|
||||||
user1.VirtualFolders[0].FsConfig.SFTPConfig.Password = kms.NewPlainSecret(defaultPassword)
|
user1.VirtualFolders[0].FsConfig.SFTPConfig.Password = kms.NewPlainSecret(defaultPassword)
|
||||||
|
|
|
@ -17,6 +17,7 @@ package common
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
@ -94,7 +95,7 @@ func (r *RateLimiterConfig) validate() error {
|
||||||
}
|
}
|
||||||
r.Protocols = util.RemoveDuplicates(r.Protocols, true)
|
r.Protocols = util.RemoveDuplicates(r.Protocols, true)
|
||||||
for _, protocol := range r.Protocols {
|
for _, protocol := range r.Protocols {
|
||||||
if !util.Contains(rateLimiterProtocolValues, protocol) {
|
if !slices.Contains(rateLimiterProtocolValues, protocol) {
|
||||||
return fmt.Errorf("invalid protocol %q", protocol)
|
return fmt.Errorf("invalid protocol %q", protocol)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/v2/internal/logger"
|
"github.com/drakkan/sftpgo/v2/internal/logger"
|
||||||
|
@ -96,7 +97,7 @@ func (m *CertManager) loadCertificates() error {
|
||||||
}
|
}
|
||||||
logger.Debug(m.logSender, "", "TLS certificate %q successfully loaded, id %v", keyPair.Cert, keyPair.ID)
|
logger.Debug(m.logSender, "", "TLS certificate %q successfully loaded, id %v", keyPair.Cert, keyPair.ID)
|
||||||
certs[keyPair.ID] = &newCert
|
certs[keyPair.ID] = &newCert
|
||||||
if !util.Contains(m.monitorList, keyPair.Cert) {
|
if !slices.Contains(m.monitorList, keyPair.Cert) {
|
||||||
m.monitorList = append(m.monitorList, keyPair.Cert)
|
m.monitorList = append(m.monitorList, keyPair.Cert)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -190,7 +191,7 @@ func (m *CertManager) LoadCRLs() error {
|
||||||
|
|
||||||
logger.Debug(m.logSender, "", "CRL %q successfully loaded", revocationList)
|
logger.Debug(m.logSender, "", "CRL %q successfully loaded", revocationList)
|
||||||
crls = append(crls, crl)
|
crls = append(crls, crl)
|
||||||
if !util.Contains(m.monitorList, revocationList) {
|
if !slices.Contains(m.monitorList, revocationList) {
|
||||||
m.monitorList = append(m.monitorList, revocationList)
|
m.monitorList = append(m.monitorList, revocationList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -716,7 +717,7 @@ func checkOverrideDefaultSettings() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if util.Contains(viper.AllKeys(), "mfa.totp") {
|
if slices.Contains(viper.AllKeys(), "mfa.totp") {
|
||||||
globalConf.MFAConfig.TOTP = nil
|
globalConf.MFAConfig.TOTP = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/sftpgo/sdk/kms"
|
"github.com/sftpgo/sdk/kms"
|
||||||
|
@ -36,7 +37,6 @@ import (
|
||||||
"github.com/drakkan/sftpgo/v2/internal/plugin"
|
"github.com/drakkan/sftpgo/v2/internal/plugin"
|
||||||
"github.com/drakkan/sftpgo/v2/internal/sftpd"
|
"github.com/drakkan/sftpgo/v2/internal/sftpd"
|
||||||
"github.com/drakkan/sftpgo/v2/internal/smtp"
|
"github.com/drakkan/sftpgo/v2/internal/smtp"
|
||||||
"github.com/drakkan/sftpgo/v2/internal/util"
|
|
||||||
"github.com/drakkan/sftpgo/v2/internal/webdavd"
|
"github.com/drakkan/sftpgo/v2/internal/webdavd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -679,8 +679,8 @@ func TestPluginsFromEnv(t *testing.T) {
|
||||||
pluginConf := pluginsConf[0]
|
pluginConf := pluginsConf[0]
|
||||||
require.Equal(t, "notifier", pluginConf.Type)
|
require.Equal(t, "notifier", pluginConf.Type)
|
||||||
require.Len(t, pluginConf.NotifierOptions.FsEvents, 2)
|
require.Len(t, pluginConf.NotifierOptions.FsEvents, 2)
|
||||||
require.True(t, util.Contains(pluginConf.NotifierOptions.FsEvents, "upload"))
|
require.True(t, slices.Contains(pluginConf.NotifierOptions.FsEvents, "upload"))
|
||||||
require.True(t, util.Contains(pluginConf.NotifierOptions.FsEvents, "download"))
|
require.True(t, slices.Contains(pluginConf.NotifierOptions.FsEvents, "download"))
|
||||||
require.Len(t, pluginConf.NotifierOptions.ProviderEvents, 2)
|
require.Len(t, pluginConf.NotifierOptions.ProviderEvents, 2)
|
||||||
require.Equal(t, "add", pluginConf.NotifierOptions.ProviderEvents[0])
|
require.Equal(t, "add", pluginConf.NotifierOptions.ProviderEvents[0])
|
||||||
require.Equal(t, "update", pluginConf.NotifierOptions.ProviderEvents[1])
|
require.Equal(t, "update", pluginConf.NotifierOptions.ProviderEvents[1])
|
||||||
|
@ -729,8 +729,8 @@ func TestPluginsFromEnv(t *testing.T) {
|
||||||
pluginConf = pluginsConf[0]
|
pluginConf = pluginsConf[0]
|
||||||
require.Equal(t, "notifier", pluginConf.Type)
|
require.Equal(t, "notifier", pluginConf.Type)
|
||||||
require.Len(t, pluginConf.NotifierOptions.FsEvents, 2)
|
require.Len(t, pluginConf.NotifierOptions.FsEvents, 2)
|
||||||
require.True(t, util.Contains(pluginConf.NotifierOptions.FsEvents, "upload"))
|
require.True(t, slices.Contains(pluginConf.NotifierOptions.FsEvents, "upload"))
|
||||||
require.True(t, util.Contains(pluginConf.NotifierOptions.FsEvents, "download"))
|
require.True(t, slices.Contains(pluginConf.NotifierOptions.FsEvents, "download"))
|
||||||
require.Len(t, pluginConf.NotifierOptions.ProviderEvents, 2)
|
require.Len(t, pluginConf.NotifierOptions.ProviderEvents, 2)
|
||||||
require.Equal(t, "add", pluginConf.NotifierOptions.ProviderEvents[0])
|
require.Equal(t, "add", pluginConf.NotifierOptions.ProviderEvents[0])
|
||||||
require.Equal(t, "update", pluginConf.NotifierOptions.ProviderEvents[1])
|
require.Equal(t, "update", pluginConf.NotifierOptions.ProviderEvents[1])
|
||||||
|
@ -787,8 +787,8 @@ func TestRateLimitersFromEnv(t *testing.T) {
|
||||||
require.Equal(t, 2, limiters[0].Type)
|
require.Equal(t, 2, limiters[0].Type)
|
||||||
protocols := limiters[0].Protocols
|
protocols := limiters[0].Protocols
|
||||||
require.Len(t, protocols, 2)
|
require.Len(t, protocols, 2)
|
||||||
require.True(t, util.Contains(protocols, common.ProtocolFTP))
|
require.True(t, slices.Contains(protocols, common.ProtocolFTP))
|
||||||
require.True(t, util.Contains(protocols, common.ProtocolSSH))
|
require.True(t, slices.Contains(protocols, common.ProtocolSSH))
|
||||||
require.True(t, limiters[0].GenerateDefenderEvents)
|
require.True(t, limiters[0].GenerateDefenderEvents)
|
||||||
require.Equal(t, 50, limiters[0].EntriesSoftLimit)
|
require.Equal(t, 50, limiters[0].EntriesSoftLimit)
|
||||||
require.Equal(t, 100, limiters[0].EntriesHardLimit)
|
require.Equal(t, 100, limiters[0].EntriesHardLimit)
|
||||||
|
@ -799,10 +799,10 @@ func TestRateLimitersFromEnv(t *testing.T) {
|
||||||
require.Equal(t, 2, limiters[1].Type)
|
require.Equal(t, 2, limiters[1].Type)
|
||||||
protocols = limiters[1].Protocols
|
protocols = limiters[1].Protocols
|
||||||
require.Len(t, protocols, 4)
|
require.Len(t, protocols, 4)
|
||||||
require.True(t, util.Contains(protocols, common.ProtocolFTP))
|
require.True(t, slices.Contains(protocols, common.ProtocolFTP))
|
||||||
require.True(t, util.Contains(protocols, common.ProtocolSSH))
|
require.True(t, slices.Contains(protocols, common.ProtocolSSH))
|
||||||
require.True(t, util.Contains(protocols, common.ProtocolWebDAV))
|
require.True(t, slices.Contains(protocols, common.ProtocolWebDAV))
|
||||||
require.True(t, util.Contains(protocols, common.ProtocolHTTP))
|
require.True(t, slices.Contains(protocols, common.ProtocolHTTP))
|
||||||
require.False(t, limiters[1].GenerateDefenderEvents)
|
require.False(t, limiters[1].GenerateDefenderEvents)
|
||||||
require.Equal(t, 100, limiters[1].EntriesSoftLimit)
|
require.Equal(t, 100, limiters[1].EntriesSoftLimit)
|
||||||
require.Equal(t, 150, limiters[1].EntriesHardLimit)
|
require.Equal(t, 150, limiters[1].EntriesHardLimit)
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -78,8 +79,8 @@ func executeAction(operation, executor, ip, objectType, objectName, role string,
|
||||||
if config.Actions.Hook == "" {
|
if config.Actions.Hook == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !util.Contains(config.Actions.ExecuteOn, operation) ||
|
if !slices.Contains(config.Actions.ExecuteOn, operation) ||
|
||||||
!util.Contains(config.Actions.ExecuteFor, objectType) {
|
!slices.Contains(config.Actions.ExecuteFor, objectType) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -96,7 +97,7 @@ func (c *AdminTOTPConfig) validate(username string) error {
|
||||||
if c.ConfigName == "" {
|
if c.ConfigName == "" {
|
||||||
return util.NewValidationError("totp: config name is mandatory")
|
return util.NewValidationError("totp: config name is mandatory")
|
||||||
}
|
}
|
||||||
if !util.Contains(mfa.GetAvailableTOTPConfigNames(), c.ConfigName) {
|
if !slices.Contains(mfa.GetAvailableTOTPConfigNames(), c.ConfigName) {
|
||||||
return util.NewValidationError(fmt.Sprintf("totp: config name %q not found", c.ConfigName))
|
return util.NewValidationError(fmt.Sprintf("totp: config name %q not found", c.ConfigName))
|
||||||
}
|
}
|
||||||
if c.Secret.IsEmpty() {
|
if c.Secret.IsEmpty() {
|
||||||
|
@ -337,15 +338,15 @@ func (a *Admin) validatePermissions() error {
|
||||||
util.I18nErrorPermissionsRequired,
|
util.I18nErrorPermissionsRequired,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if util.Contains(a.Permissions, PermAdminAny) {
|
if slices.Contains(a.Permissions, PermAdminAny) {
|
||||||
a.Permissions = []string{PermAdminAny}
|
a.Permissions = []string{PermAdminAny}
|
||||||
}
|
}
|
||||||
for _, perm := range a.Permissions {
|
for _, perm := range a.Permissions {
|
||||||
if !util.Contains(validAdminPerms, perm) {
|
if !slices.Contains(validAdminPerms, perm) {
|
||||||
return util.NewValidationError(fmt.Sprintf("invalid permission: %q", perm))
|
return util.NewValidationError(fmt.Sprintf("invalid permission: %q", perm))
|
||||||
}
|
}
|
||||||
if a.Role != "" {
|
if a.Role != "" {
|
||||||
if util.Contains(forbiddenPermsForRoleAdmins, perm) {
|
if slices.Contains(forbiddenPermsForRoleAdmins, perm) {
|
||||||
deniedPerms := strings.Join(forbiddenPermsForRoleAdmins, ",")
|
deniedPerms := strings.Join(forbiddenPermsForRoleAdmins, ",")
|
||||||
return util.NewI18nError(
|
return util.NewI18nError(
|
||||||
util.NewValidationError(fmt.Sprintf("a role admin cannot have the following permissions: %q", deniedPerms)),
|
util.NewValidationError(fmt.Sprintf("a role admin cannot have the following permissions: %q", deniedPerms)),
|
||||||
|
@ -559,10 +560,10 @@ func (a *Admin) SetNilSecretsIfEmpty() {
|
||||||
|
|
||||||
// HasPermission returns true if the admin has the specified permission
|
// HasPermission returns true if the admin has the specified permission
|
||||||
func (a *Admin) HasPermission(perm string) bool {
|
func (a *Admin) HasPermission(perm string) bool {
|
||||||
if util.Contains(a.Permissions, PermAdminAny) {
|
if slices.Contains(a.Permissions, PermAdminAny) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return util.Contains(a.Permissions, perm)
|
return slices.Contains(a.Permissions, perm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAllowedIPAsString returns the allowed IP as comma separated string
|
// GetAllowedIPAsString returns the allowed IP as comma separated string
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -3320,7 +3321,7 @@ func (p *BoltProvider) addAdminToRole(username, roleName string, bucket *bolt.Bu
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !util.Contains(role.Admins, username) {
|
if !slices.Contains(role.Admins, username) {
|
||||||
role.Admins = append(role.Admins, username)
|
role.Admins = append(role.Admins, username)
|
||||||
buf, err := json.Marshal(role)
|
buf, err := json.Marshal(role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -3345,7 +3346,7 @@ func (p *BoltProvider) removeAdminFromRole(username, roleName string, bucket *bo
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if util.Contains(role.Admins, username) {
|
if slices.Contains(role.Admins, username) {
|
||||||
var admins []string
|
var admins []string
|
||||||
for _, admin := range role.Admins {
|
for _, admin := range role.Admins {
|
||||||
if admin != username {
|
if admin != username {
|
||||||
|
@ -3375,7 +3376,7 @@ func (p *BoltProvider) addUserToRole(username, roleName string, bucket *bolt.Buc
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !util.Contains(role.Users, username) {
|
if !slices.Contains(role.Users, username) {
|
||||||
role.Users = append(role.Users, username)
|
role.Users = append(role.Users, username)
|
||||||
buf, err := json.Marshal(role)
|
buf, err := json.Marshal(role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -3400,7 +3401,7 @@ func (p *BoltProvider) removeUserFromRole(username, roleName string, bucket *bol
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if util.Contains(role.Users, username) {
|
if slices.Contains(role.Users, username) {
|
||||||
var users []string
|
var users []string
|
||||||
for _, user := range role.Users {
|
for _, user := range role.Users {
|
||||||
if user != username {
|
if user != username {
|
||||||
|
@ -3428,7 +3429,7 @@ func (p *BoltProvider) addRuleToActionMapping(ruleName, actionName string, bucke
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !util.Contains(action.Rules, ruleName) {
|
if !slices.Contains(action.Rules, ruleName) {
|
||||||
action.Rules = append(action.Rules, ruleName)
|
action.Rules = append(action.Rules, ruleName)
|
||||||
buf, err := json.Marshal(action)
|
buf, err := json.Marshal(action)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -3450,7 +3451,7 @@ func (p *BoltProvider) removeRuleFromActionMapping(ruleName, actionName string,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if util.Contains(action.Rules, ruleName) {
|
if slices.Contains(action.Rules, ruleName) {
|
||||||
var rules []string
|
var rules []string
|
||||||
for _, r := range action.Rules {
|
for _, r := range action.Rules {
|
||||||
if r != ruleName {
|
if r != ruleName {
|
||||||
|
@ -3477,7 +3478,7 @@ func (p *BoltProvider) addUserToGroupMapping(username, groupname string, bucket
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !util.Contains(group.Users, username) {
|
if !slices.Contains(group.Users, username) {
|
||||||
group.Users = append(group.Users, username)
|
group.Users = append(group.Users, username)
|
||||||
buf, err := json.Marshal(group)
|
buf, err := json.Marshal(group)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -3522,7 +3523,7 @@ func (p *BoltProvider) addAdminToGroupMapping(username, groupname string, bucket
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !util.Contains(group.Admins, username) {
|
if !slices.Contains(group.Admins, username) {
|
||||||
group.Admins = append(group.Admins, username)
|
group.Admins = append(group.Admins, username)
|
||||||
buf, err := json.Marshal(group)
|
buf, err := json.Marshal(group)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -3593,11 +3594,11 @@ func (p *BoltProvider) addRelationToFolderMapping(folderName string, user *User,
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
updated := false
|
updated := false
|
||||||
if user != nil && !util.Contains(folder.Users, user.Username) {
|
if user != nil && !slices.Contains(folder.Users, user.Username) {
|
||||||
folder.Users = append(folder.Users, user.Username)
|
folder.Users = append(folder.Users, user.Username)
|
||||||
updated = true
|
updated = true
|
||||||
}
|
}
|
||||||
if group != nil && !util.Contains(folder.Groups, group.Name) {
|
if group != nil && !slices.Contains(folder.Groups, group.Name) {
|
||||||
folder.Groups = append(folder.Groups, group.Name)
|
folder.Groups = append(folder.Groups, group.Name)
|
||||||
updated = true
|
updated = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image/png"
|
"image/png"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
|
@ -105,7 +106,7 @@ func (c *SFTPDConfigs) validate() error {
|
||||||
if algo == ssh.CertAlgoRSAv01 {
|
if algo == ssh.CertAlgoRSAv01 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !util.Contains(supportedHostKeyAlgos, algo) {
|
if !slices.Contains(supportedHostKeyAlgos, algo) {
|
||||||
return util.NewValidationError(fmt.Sprintf("unsupported host key algorithm %q", algo))
|
return util.NewValidationError(fmt.Sprintf("unsupported host key algorithm %q", algo))
|
||||||
}
|
}
|
||||||
hostKeyAlgos = append(hostKeyAlgos, algo)
|
hostKeyAlgos = append(hostKeyAlgos, algo)
|
||||||
|
@ -116,24 +117,24 @@ func (c *SFTPDConfigs) validate() error {
|
||||||
if algo == "diffie-hellman-group18-sha512" || algo == ssh.KeyExchangeDHGEXSHA256 {
|
if algo == "diffie-hellman-group18-sha512" || algo == ssh.KeyExchangeDHGEXSHA256 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !util.Contains(supportedKexAlgos, algo) {
|
if !slices.Contains(supportedKexAlgos, algo) {
|
||||||
return util.NewValidationError(fmt.Sprintf("unsupported KEX algorithm %q", algo))
|
return util.NewValidationError(fmt.Sprintf("unsupported KEX algorithm %q", algo))
|
||||||
}
|
}
|
||||||
kexAlgos = append(kexAlgos, algo)
|
kexAlgos = append(kexAlgos, algo)
|
||||||
}
|
}
|
||||||
c.KexAlgorithms = kexAlgos
|
c.KexAlgorithms = kexAlgos
|
||||||
for _, cipher := range c.Ciphers {
|
for _, cipher := range c.Ciphers {
|
||||||
if !util.Contains(supportedCiphers, cipher) {
|
if !slices.Contains(supportedCiphers, cipher) {
|
||||||
return util.NewValidationError(fmt.Sprintf("unsupported cipher %q", cipher))
|
return util.NewValidationError(fmt.Sprintf("unsupported cipher %q", cipher))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, mac := range c.MACs {
|
for _, mac := range c.MACs {
|
||||||
if !util.Contains(supportedMACs, mac) {
|
if !slices.Contains(supportedMACs, mac) {
|
||||||
return util.NewValidationError(fmt.Sprintf("unsupported MAC algorithm %q", mac))
|
return util.NewValidationError(fmt.Sprintf("unsupported MAC algorithm %q", mac))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, algo := range c.PublicKeyAlgos {
|
for _, algo := range c.PublicKeyAlgos {
|
||||||
if !util.Contains(supportedPublicKeyAlgos, algo) {
|
if !slices.Contains(supportedPublicKeyAlgos, algo) {
|
||||||
return util.NewValidationError(fmt.Sprintf("unsupported public key algorithm %q", algo))
|
return util.NewValidationError(fmt.Sprintf("unsupported public key algorithm %q", algo))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -519,7 +520,7 @@ type Config struct {
|
||||||
// GetShared returns the provider share mode.
|
// GetShared returns the provider share mode.
|
||||||
// This method is called before the provider is initialized
|
// This method is called before the provider is initialized
|
||||||
func (c *Config) GetShared() int {
|
func (c *Config) GetShared() int {
|
||||||
if !util.Contains(sharedProviders, c.Driver) {
|
if !slices.Contains(sharedProviders, c.Driver) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return c.IsShared
|
return c.IsShared
|
||||||
|
@ -885,7 +886,7 @@ func SetTempPath(fsPath string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkSharedMode() {
|
func checkSharedMode() {
|
||||||
if !util.Contains(sharedProviders, config.Driver) {
|
if !slices.Contains(sharedProviders, config.Driver) {
|
||||||
config.IsShared = 0
|
config.IsShared = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1714,7 +1715,7 @@ func IPListEntryExists(ipOrNet string, listType IPListType) (IPListEntry, error)
|
||||||
|
|
||||||
// GetIPListEntries returns the IP list entries applying the specified criteria and search limit
|
// GetIPListEntries returns the IP list entries applying the specified criteria and search limit
|
||||||
func GetIPListEntries(listType IPListType, filter, from, order string, limit int) ([]IPListEntry, error) {
|
func GetIPListEntries(listType IPListType, filter, from, order string, limit int) ([]IPListEntry, error) {
|
||||||
if !util.Contains(supportedIPListType, listType) {
|
if !slices.Contains(supportedIPListType, listType) {
|
||||||
return nil, util.NewValidationError(fmt.Sprintf("invalid list type %d", listType))
|
return nil, util.NewValidationError(fmt.Sprintf("invalid list type %d", listType))
|
||||||
}
|
}
|
||||||
return provider.getIPListEntries(listType, filter, from, order, limit)
|
return provider.getIPListEntries(listType, filter, from, order, limit)
|
||||||
|
@ -2373,7 +2374,7 @@ func GetFolders(limit, offset int, order string, minimal bool) ([]vfs.BaseVirtua
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpUsers(data *BackupData, scopes []string) error {
|
func dumpUsers(data *BackupData, scopes []string) error {
|
||||||
if len(scopes) == 0 || util.Contains(scopes, DumpScopeUsers) {
|
if len(scopes) == 0 || slices.Contains(scopes, DumpScopeUsers) {
|
||||||
users, err := provider.dumpUsers()
|
users, err := provider.dumpUsers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -2384,7 +2385,7 @@ func dumpUsers(data *BackupData, scopes []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpFolders(data *BackupData, scopes []string) error {
|
func dumpFolders(data *BackupData, scopes []string) error {
|
||||||
if len(scopes) == 0 || util.Contains(scopes, DumpScopeFolders) {
|
if len(scopes) == 0 || slices.Contains(scopes, DumpScopeFolders) {
|
||||||
folders, err := provider.dumpFolders()
|
folders, err := provider.dumpFolders()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -2395,7 +2396,7 @@ func dumpFolders(data *BackupData, scopes []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpGroups(data *BackupData, scopes []string) error {
|
func dumpGroups(data *BackupData, scopes []string) error {
|
||||||
if len(scopes) == 0 || util.Contains(scopes, DumpScopeGroups) {
|
if len(scopes) == 0 || slices.Contains(scopes, DumpScopeGroups) {
|
||||||
groups, err := provider.dumpGroups()
|
groups, err := provider.dumpGroups()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -2406,7 +2407,7 @@ func dumpGroups(data *BackupData, scopes []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpAdmins(data *BackupData, scopes []string) error {
|
func dumpAdmins(data *BackupData, scopes []string) error {
|
||||||
if len(scopes) == 0 || util.Contains(scopes, DumpScopeAdmins) {
|
if len(scopes) == 0 || slices.Contains(scopes, DumpScopeAdmins) {
|
||||||
admins, err := provider.dumpAdmins()
|
admins, err := provider.dumpAdmins()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -2417,7 +2418,7 @@ func dumpAdmins(data *BackupData, scopes []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpAPIKeys(data *BackupData, scopes []string) error {
|
func dumpAPIKeys(data *BackupData, scopes []string) error {
|
||||||
if len(scopes) == 0 || util.Contains(scopes, DumpScopeAPIKeys) {
|
if len(scopes) == 0 || slices.Contains(scopes, DumpScopeAPIKeys) {
|
||||||
apiKeys, err := provider.dumpAPIKeys()
|
apiKeys, err := provider.dumpAPIKeys()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -2428,7 +2429,7 @@ func dumpAPIKeys(data *BackupData, scopes []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpShares(data *BackupData, scopes []string) error {
|
func dumpShares(data *BackupData, scopes []string) error {
|
||||||
if len(scopes) == 0 || util.Contains(scopes, DumpScopeShares) {
|
if len(scopes) == 0 || slices.Contains(scopes, DumpScopeShares) {
|
||||||
shares, err := provider.dumpShares()
|
shares, err := provider.dumpShares()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -2439,7 +2440,7 @@ func dumpShares(data *BackupData, scopes []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpActions(data *BackupData, scopes []string) error {
|
func dumpActions(data *BackupData, scopes []string) error {
|
||||||
if len(scopes) == 0 || util.Contains(scopes, DumpScopeActions) {
|
if len(scopes) == 0 || slices.Contains(scopes, DumpScopeActions) {
|
||||||
actions, err := provider.dumpEventActions()
|
actions, err := provider.dumpEventActions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -2450,7 +2451,7 @@ func dumpActions(data *BackupData, scopes []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpRules(data *BackupData, scopes []string) error {
|
func dumpRules(data *BackupData, scopes []string) error {
|
||||||
if len(scopes) == 0 || util.Contains(scopes, DumpScopeRules) {
|
if len(scopes) == 0 || slices.Contains(scopes, DumpScopeRules) {
|
||||||
rules, err := provider.dumpEventRules()
|
rules, err := provider.dumpEventRules()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -2461,7 +2462,7 @@ func dumpRules(data *BackupData, scopes []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpRoles(data *BackupData, scopes []string) error {
|
func dumpRoles(data *BackupData, scopes []string) error {
|
||||||
if len(scopes) == 0 || util.Contains(scopes, DumpScopeRoles) {
|
if len(scopes) == 0 || slices.Contains(scopes, DumpScopeRoles) {
|
||||||
roles, err := provider.dumpRoles()
|
roles, err := provider.dumpRoles()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -2472,7 +2473,7 @@ func dumpRoles(data *BackupData, scopes []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpIPLists(data *BackupData, scopes []string) error {
|
func dumpIPLists(data *BackupData, scopes []string) error {
|
||||||
if len(scopes) == 0 || util.Contains(scopes, DumpScopeIPLists) {
|
if len(scopes) == 0 || slices.Contains(scopes, DumpScopeIPLists) {
|
||||||
ipLists, err := provider.dumpIPListEntries()
|
ipLists, err := provider.dumpIPListEntries()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -2483,7 +2484,7 @@ func dumpIPLists(data *BackupData, scopes []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpConfigs(data *BackupData, scopes []string) error {
|
func dumpConfigs(data *BackupData, scopes []string) error {
|
||||||
if len(scopes) == 0 || util.Contains(scopes, DumpScopeConfigs) {
|
if len(scopes) == 0 || slices.Contains(scopes, DumpScopeConfigs) {
|
||||||
configs, err := provider.getConfigs()
|
configs, err := provider.getConfigs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -2787,7 +2788,7 @@ func validateUserTOTPConfig(c *UserTOTPConfig, username string) error {
|
||||||
if c.ConfigName == "" {
|
if c.ConfigName == "" {
|
||||||
return util.NewValidationError("totp: config name is mandatory")
|
return util.NewValidationError("totp: config name is mandatory")
|
||||||
}
|
}
|
||||||
if !util.Contains(mfa.GetAvailableTOTPConfigNames(), c.ConfigName) {
|
if !slices.Contains(mfa.GetAvailableTOTPConfigNames(), c.ConfigName) {
|
||||||
return util.NewValidationError(fmt.Sprintf("totp: config name %q not found", c.ConfigName))
|
return util.NewValidationError(fmt.Sprintf("totp: config name %q not found", c.ConfigName))
|
||||||
}
|
}
|
||||||
if c.Secret.IsEmpty() {
|
if c.Secret.IsEmpty() {
|
||||||
|
@ -2803,7 +2804,7 @@ func validateUserTOTPConfig(c *UserTOTPConfig, username string) error {
|
||||||
return util.NewValidationError("totp: specify at least one protocol")
|
return util.NewValidationError("totp: specify at least one protocol")
|
||||||
}
|
}
|
||||||
for _, protocol := range c.Protocols {
|
for _, protocol := range c.Protocols {
|
||||||
if !util.Contains(MFAProtocols, protocol) {
|
if !slices.Contains(MFAProtocols, protocol) {
|
||||||
return util.NewValidationError(fmt.Sprintf("totp: invalid protocol %q", protocol))
|
return util.NewValidationError(fmt.Sprintf("totp: invalid protocol %q", protocol))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2836,7 +2837,7 @@ func validateUserPermissions(permsToCheck map[string][]string) (map[string][]str
|
||||||
return permissions, util.NewValidationError("invalid permissions")
|
return permissions, util.NewValidationError("invalid permissions")
|
||||||
}
|
}
|
||||||
for _, p := range perms {
|
for _, p := range perms {
|
||||||
if !util.Contains(ValidPerms, p) {
|
if !slices.Contains(ValidPerms, p) {
|
||||||
return permissions, util.NewValidationError(fmt.Sprintf("invalid permission: %q", p))
|
return permissions, util.NewValidationError(fmt.Sprintf("invalid permission: %q", p))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2850,7 +2851,7 @@ func validateUserPermissions(permsToCheck map[string][]string) (map[string][]str
|
||||||
if dir != cleanedDir && cleanedDir == "/" {
|
if dir != cleanedDir && cleanedDir == "/" {
|
||||||
return permissions, util.NewValidationError(fmt.Sprintf("cannot set permissions for invalid subdirectory: %q is an alias for \"/\"", dir))
|
return permissions, util.NewValidationError(fmt.Sprintf("cannot set permissions for invalid subdirectory: %q is an alias for \"/\"", dir))
|
||||||
}
|
}
|
||||||
if util.Contains(perms, PermAny) {
|
if slices.Contains(perms, PermAny) {
|
||||||
permissions[cleanedDir] = []string{PermAny}
|
permissions[cleanedDir] = []string{PermAny}
|
||||||
} else {
|
} else {
|
||||||
permissions[cleanedDir] = util.RemoveDuplicates(perms, false)
|
permissions[cleanedDir] = util.RemoveDuplicates(perms, false)
|
||||||
|
@ -2926,7 +2927,7 @@ func validateFiltersPatternExtensions(baseFilters *sdk.BaseUserFilters) error {
|
||||||
util.I18nErrorFilePatternPathInvalid,
|
util.I18nErrorFilePatternPathInvalid,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if util.Contains(filteredPaths, cleanedPath) {
|
if slices.Contains(filteredPaths, cleanedPath) {
|
||||||
return util.NewI18nError(
|
return util.NewI18nError(
|
||||||
util.NewValidationError(fmt.Sprintf("duplicate file patterns filter for path %q", f.Path)),
|
util.NewValidationError(fmt.Sprintf("duplicate file patterns filter for path %q", f.Path)),
|
||||||
util.I18nErrorFilePatternDuplicated,
|
util.I18nErrorFilePatternDuplicated,
|
||||||
|
@ -3045,13 +3046,13 @@ func validateFilterProtocols(filters *sdk.BaseUserFilters) error {
|
||||||
return util.NewValidationError("invalid denied_protocols")
|
return util.NewValidationError("invalid denied_protocols")
|
||||||
}
|
}
|
||||||
for _, p := range filters.DeniedProtocols {
|
for _, p := range filters.DeniedProtocols {
|
||||||
if !util.Contains(ValidProtocols, p) {
|
if !slices.Contains(ValidProtocols, p) {
|
||||||
return util.NewValidationError(fmt.Sprintf("invalid denied protocol %q", p))
|
return util.NewValidationError(fmt.Sprintf("invalid denied protocol %q", p))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range filters.TwoFactorAuthProtocols {
|
for _, p := range filters.TwoFactorAuthProtocols {
|
||||||
if !util.Contains(MFAProtocols, p) {
|
if !slices.Contains(MFAProtocols, p) {
|
||||||
return util.NewValidationError(fmt.Sprintf("invalid two factor protocol %q", p))
|
return util.NewValidationError(fmt.Sprintf("invalid two factor protocol %q", p))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3107,7 +3108,7 @@ func validateBaseFilters(filters *sdk.BaseUserFilters) error {
|
||||||
return util.NewValidationError("invalid denied_login_methods")
|
return util.NewValidationError("invalid denied_login_methods")
|
||||||
}
|
}
|
||||||
for _, loginMethod := range filters.DeniedLoginMethods {
|
for _, loginMethod := range filters.DeniedLoginMethods {
|
||||||
if !util.Contains(ValidLoginMethods, loginMethod) {
|
if !slices.Contains(ValidLoginMethods, loginMethod) {
|
||||||
return util.NewValidationError(fmt.Sprintf("invalid login method: %q", loginMethod))
|
return util.NewValidationError(fmt.Sprintf("invalid login method: %q", loginMethod))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3115,7 +3116,7 @@ func validateBaseFilters(filters *sdk.BaseUserFilters) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if filters.TLSUsername != "" {
|
if filters.TLSUsername != "" {
|
||||||
if !util.Contains(validTLSUsernames, string(filters.TLSUsername)) {
|
if !slices.Contains(validTLSUsernames, string(filters.TLSUsername)) {
|
||||||
return util.NewValidationError(fmt.Sprintf("invalid TLS username: %q", filters.TLSUsername))
|
return util.NewValidationError(fmt.Sprintf("invalid TLS username: %q", filters.TLSUsername))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3125,7 +3126,7 @@ func validateBaseFilters(filters *sdk.BaseUserFilters) error {
|
||||||
}
|
}
|
||||||
filters.TLSCerts = certs
|
filters.TLSCerts = certs
|
||||||
for _, opts := range filters.WebClient {
|
for _, opts := range filters.WebClient {
|
||||||
if !util.Contains(sdk.WebClientOptions, opts) {
|
if !slices.Contains(sdk.WebClientOptions, opts) {
|
||||||
return util.NewValidationError(fmt.Sprintf("invalid web client options %q", opts))
|
return util.NewValidationError(fmt.Sprintf("invalid web client options %q", opts))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3193,19 +3194,19 @@ func validateAccessTimeFilters(filters *sdk.BaseUserFilters) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateCombinedUserFilters(user *User) error {
|
func validateCombinedUserFilters(user *User) error {
|
||||||
if user.Filters.TOTPConfig.Enabled && util.Contains(user.Filters.WebClient, sdk.WebClientMFADisabled) {
|
if user.Filters.TOTPConfig.Enabled && slices.Contains(user.Filters.WebClient, sdk.WebClientMFADisabled) {
|
||||||
return util.NewI18nError(
|
return util.NewI18nError(
|
||||||
util.NewValidationError("two-factor authentication cannot be disabled for a user with an active configuration"),
|
util.NewValidationError("two-factor authentication cannot be disabled for a user with an active configuration"),
|
||||||
util.I18nErrorDisableActive2FA,
|
util.I18nErrorDisableActive2FA,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if user.Filters.RequirePasswordChange && util.Contains(user.Filters.WebClient, sdk.WebClientPasswordChangeDisabled) {
|
if user.Filters.RequirePasswordChange && slices.Contains(user.Filters.WebClient, sdk.WebClientPasswordChangeDisabled) {
|
||||||
return util.NewI18nError(
|
return util.NewI18nError(
|
||||||
util.NewValidationError("you cannot require password change and at the same time disallow it"),
|
util.NewValidationError("you cannot require password change and at the same time disallow it"),
|
||||||
util.I18nErrorPwdChangeConflict,
|
util.I18nErrorPwdChangeConflict,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if len(user.Filters.TwoFactorAuthProtocols) > 0 && util.Contains(user.Filters.WebClient, sdk.WebClientMFADisabled) {
|
if len(user.Filters.TwoFactorAuthProtocols) > 0 && slices.Contains(user.Filters.WebClient, sdk.WebClientMFADisabled) {
|
||||||
return util.NewI18nError(
|
return util.NewI18nError(
|
||||||
util.NewValidationError("you cannot require two-factor authentication and at the same time disallow it"),
|
util.NewValidationError("you cannot require two-factor authentication and at the same time disallow it"),
|
||||||
util.I18nError2FAConflict,
|
util.I18nError2FAConflict,
|
||||||
|
@ -3526,7 +3527,7 @@ func checkUserPasscode(user *User, password, protocol string) (string, error) {
|
||||||
if user.Filters.TOTPConfig.Enabled {
|
if user.Filters.TOTPConfig.Enabled {
|
||||||
switch protocol {
|
switch protocol {
|
||||||
case protocolFTP:
|
case protocolFTP:
|
||||||
if util.Contains(user.Filters.TOTPConfig.Protocols, protocol) {
|
if slices.Contains(user.Filters.TOTPConfig.Protocols, protocol) {
|
||||||
// the TOTP passcode has six digits
|
// the TOTP passcode has six digits
|
||||||
pwdLen := len(password)
|
pwdLen := len(password)
|
||||||
if pwdLen < 7 {
|
if pwdLen < 7 {
|
||||||
|
@ -3732,7 +3733,7 @@ func doBuiltinKeyboardInteractiveAuth(user *User, client ssh.KeyboardInteractive
|
||||||
if err := user.LoadAndApplyGroupSettings(); err != nil {
|
if err := user.LoadAndApplyGroupSettings(); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
hasSecondFactor := user.Filters.TOTPConfig.Enabled && util.Contains(user.Filters.TOTPConfig.Protocols, protocolSSH)
|
hasSecondFactor := user.Filters.TOTPConfig.Enabled && slices.Contains(user.Filters.TOTPConfig.Protocols, protocolSSH)
|
||||||
if !isPartialAuth || !hasSecondFactor {
|
if !isPartialAuth || !hasSecondFactor {
|
||||||
answers, err := client("", "", []string{"Password: "}, []bool{false})
|
answers, err := client("", "", []string{"Password: "}, []bool{false})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -3750,7 +3751,7 @@ func doBuiltinKeyboardInteractiveAuth(user *User, client ssh.KeyboardInteractive
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkKeyboardInteractiveSecondFactor(user *User, client ssh.KeyboardInteractiveChallenge, protocol string) (int, error) {
|
func checkKeyboardInteractiveSecondFactor(user *User, client ssh.KeyboardInteractiveChallenge, protocol string) (int, error) {
|
||||||
if !user.Filters.TOTPConfig.Enabled || !util.Contains(user.Filters.TOTPConfig.Protocols, protocolSSH) {
|
if !user.Filters.TOTPConfig.Enabled || !slices.Contains(user.Filters.TOTPConfig.Protocols, protocolSSH) {
|
||||||
return 1, nil
|
return 1, nil
|
||||||
}
|
}
|
||||||
err := user.Filters.TOTPConfig.Secret.TryDecrypt()
|
err := user.Filters.TOTPConfig.Secret.TryDecrypt()
|
||||||
|
@ -3874,7 +3875,7 @@ func getKeyboardInteractiveAnswers(client ssh.KeyboardInteractiveChallenge, resp
|
||||||
}
|
}
|
||||||
if len(answers) == 1 && response.CheckPwd > 0 {
|
if len(answers) == 1 && response.CheckPwd > 0 {
|
||||||
if response.CheckPwd == 2 {
|
if response.CheckPwd == 2 {
|
||||||
if !user.Filters.TOTPConfig.Enabled || !util.Contains(user.Filters.TOTPConfig.Protocols, protocolSSH) {
|
if !user.Filters.TOTPConfig.Enabled || !slices.Contains(user.Filters.TOTPConfig.Protocols, protocolSSH) {
|
||||||
providerLog(logger.LevelInfo, "keyboard interactive auth error: unable to check TOTP passcode, TOTP is not enabled for user %q",
|
providerLog(logger.LevelInfo, "keyboard interactive auth error: unable to check TOTP passcode, TOTP is not enabled for user %q",
|
||||||
user.Username)
|
user.Username)
|
||||||
return answers, errors.New("TOTP not enabled for SSH protocol")
|
return answers, errors.New("TOTP not enabled for SSH protocol")
|
||||||
|
@ -4640,7 +4641,7 @@ func getConfigPath(name, configDir string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkReservedUsernames(username string) error {
|
func checkReservedUsernames(username string) error {
|
||||||
if util.Contains(reservedUsers, username) {
|
if slices.Contains(reservedUsers, username) {
|
||||||
return util.NewValidationError("this username is reserved")
|
return util.NewValidationError("this username is reserved")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -60,7 +61,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func isActionTypeValid(action int) bool {
|
func isActionTypeValid(action int) bool {
|
||||||
return util.Contains(supportedEventActions, action)
|
return slices.Contains(supportedEventActions, action)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getActionTypeAsString(action int) string {
|
func getActionTypeAsString(action int) string {
|
||||||
|
@ -115,7 +116,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func isEventTriggerValid(trigger int) bool {
|
func isEventTriggerValid(trigger int) bool {
|
||||||
return util.Contains(supportedEventTriggers, trigger)
|
return slices.Contains(supportedEventTriggers, trigger)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTriggerTypeAsString(trigger int) string {
|
func getTriggerTypeAsString(trigger int) string {
|
||||||
|
@ -169,7 +170,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func isFilesystemActionValid(value int) bool {
|
func isFilesystemActionValid(value int) bool {
|
||||||
return util.Contains(supportedFsActions, value)
|
return slices.Contains(supportedFsActions, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFsActionTypeAsString(value int) string {
|
func getFsActionTypeAsString(value int) string {
|
||||||
|
@ -380,7 +381,7 @@ func (c *EventActionHTTPConfig) validate(additionalData string) error {
|
||||||
return util.NewValidationError(fmt.Sprintf("could not encrypt HTTP password: %v", err))
|
return util.NewValidationError(fmt.Sprintf("could not encrypt HTTP password: %v", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !util.Contains(SupportedHTTPActionMethods, c.Method) {
|
if !slices.Contains(SupportedHTTPActionMethods, c.Method) {
|
||||||
return util.NewValidationError(fmt.Sprintf("unsupported HTTP method: %s", c.Method))
|
return util.NewValidationError(fmt.Sprintf("unsupported HTTP method: %s", c.Method))
|
||||||
}
|
}
|
||||||
for _, kv := range c.QueryParameters {
|
for _, kv := range c.QueryParameters {
|
||||||
|
@ -1280,7 +1281,7 @@ func (a *EventAction) validateAssociation(trigger int, fsEvents []string) error
|
||||||
}
|
}
|
||||||
if trigger == EventTriggerFsEvent {
|
if trigger == EventTriggerFsEvent {
|
||||||
for _, ev := range fsEvents {
|
for _, ev := range fsEvents {
|
||||||
if !util.Contains(allowedSyncFsEvents, ev) {
|
if !slices.Contains(allowedSyncFsEvents, ev) {
|
||||||
return util.NewI18nError(
|
return util.NewI18nError(
|
||||||
util.NewValidationError("sync execution is only supported for upload and pre-* events"),
|
util.NewValidationError("sync execution is only supported for upload and pre-* events"),
|
||||||
util.I18nErrorEvSyncUnsupportedFs,
|
util.I18nErrorEvSyncUnsupportedFs,
|
||||||
|
@ -1361,12 +1362,12 @@ func (f *ConditionOptions) validate() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range f.Protocols {
|
for _, p := range f.Protocols {
|
||||||
if !util.Contains(SupportedRuleConditionProtocols, p) {
|
if !slices.Contains(SupportedRuleConditionProtocols, p) {
|
||||||
return util.NewValidationError(fmt.Sprintf("unsupported rule condition protocol: %q", p))
|
return util.NewValidationError(fmt.Sprintf("unsupported rule condition protocol: %q", p))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, p := range f.ProviderObjects {
|
for _, p := range f.ProviderObjects {
|
||||||
if !util.Contains(SupporteRuleConditionProviderObjects, p) {
|
if !slices.Contains(SupporteRuleConditionProviderObjects, p) {
|
||||||
return util.NewValidationError(fmt.Sprintf("unsupported provider object: %q", p))
|
return util.NewValidationError(fmt.Sprintf("unsupported provider object: %q", p))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1468,7 +1469,7 @@ func (c *EventConditions) validate(trigger int) error {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
for _, ev := range c.FsEvents {
|
for _, ev := range c.FsEvents {
|
||||||
if !util.Contains(SupportedFsEvents, ev) {
|
if !slices.Contains(SupportedFsEvents, ev) {
|
||||||
return util.NewValidationError(fmt.Sprintf("unsupported fs event: %q", ev))
|
return util.NewValidationError(fmt.Sprintf("unsupported fs event: %q", ev))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1488,7 +1489,7 @@ func (c *EventConditions) validate(trigger int) error {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
for _, ev := range c.ProviderEvents {
|
for _, ev := range c.ProviderEvents {
|
||||||
if !util.Contains(SupportedProviderEvents, ev) {
|
if !slices.Contains(SupportedProviderEvents, ev) {
|
||||||
return util.NewValidationError(fmt.Sprintf("unsupported provider event: %q", ev))
|
return util.NewValidationError(fmt.Sprintf("unsupported provider event: %q", ev))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1537,7 +1538,7 @@ func (c *EventConditions) validate(trigger int) error {
|
||||||
c.Options.MinFileSize = 0
|
c.Options.MinFileSize = 0
|
||||||
c.Options.MaxFileSize = 0
|
c.Options.MaxFileSize = 0
|
||||||
c.Schedules = nil
|
c.Schedules = nil
|
||||||
if !util.Contains(supportedIDPLoginEvents, c.IDPLoginEvent) {
|
if !slices.Contains(supportedIDPLoginEvents, c.IDPLoginEvent) {
|
||||||
return util.NewValidationError(fmt.Sprintf("invalid Identity Provider login event %d", c.IDPLoginEvent))
|
return util.NewValidationError(fmt.Sprintf("invalid Identity Provider login event %d", c.IDPLoginEvent))
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -1690,7 +1691,7 @@ func (r *EventRule) validateMandatorySyncActions() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, ev := range r.Conditions.FsEvents {
|
for _, ev := range r.Conditions.FsEvents {
|
||||||
if util.Contains(mandatorySyncFsEvents, ev) {
|
if slices.Contains(mandatorySyncFsEvents, ev) {
|
||||||
return util.NewI18nError(
|
return util.NewI18nError(
|
||||||
util.NewValidationError(fmt.Sprintf("event %q requires at least a sync action", ev)),
|
util.NewValidationError(fmt.Sprintf("event %q requires at least a sync action", ev)),
|
||||||
util.I18nErrorRuleSyncActionRequired,
|
util.I18nErrorRuleSyncActionRequired,
|
||||||
|
@ -1708,7 +1709,7 @@ func (r *EventRule) checkIPBlockedAndCertificateActions() error {
|
||||||
ActionTypeDataRetentionCheck, ActionTypeFilesystem, ActionTypePasswordExpirationCheck,
|
ActionTypeDataRetentionCheck, ActionTypeFilesystem, ActionTypePasswordExpirationCheck,
|
||||||
ActionTypeUserExpirationCheck}
|
ActionTypeUserExpirationCheck}
|
||||||
for _, action := range r.Actions {
|
for _, action := range r.Actions {
|
||||||
if util.Contains(unavailableActions, action.Type) {
|
if slices.Contains(unavailableActions, action.Type) {
|
||||||
return fmt.Errorf("action %q, type %q is not supported for event trigger %q",
|
return fmt.Errorf("action %q, type %q is not supported for event trigger %q",
|
||||||
action.Name, getActionTypeAsString(action.Type), getTriggerTypeAsString(r.Trigger))
|
action.Name, getActionTypeAsString(action.Type), getTriggerTypeAsString(r.Trigger))
|
||||||
}
|
}
|
||||||
|
@ -1724,7 +1725,7 @@ func (r *EventRule) checkProviderEventActions(providerObjectType string) error {
|
||||||
ActionTypeDataRetentionCheck, ActionTypeFilesystem,
|
ActionTypeDataRetentionCheck, ActionTypeFilesystem,
|
||||||
ActionTypePasswordExpirationCheck, ActionTypeUserExpirationCheck}
|
ActionTypePasswordExpirationCheck, ActionTypeUserExpirationCheck}
|
||||||
for _, action := range r.Actions {
|
for _, action := range r.Actions {
|
||||||
if util.Contains(userSpecificActions, action.Type) && providerObjectType != actionObjectUser {
|
if slices.Contains(userSpecificActions, action.Type) && providerObjectType != actionObjectUser {
|
||||||
return fmt.Errorf("action %q, type %q is only supported for provider user events",
|
return fmt.Errorf("action %q, type %q is only supported for provider user events",
|
||||||
action.Name, getActionTypeAsString(action.Type))
|
action.Name, getActionTypeAsString(action.Type))
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
@ -85,7 +86,7 @@ var (
|
||||||
|
|
||||||
// CheckIPListType returns an error if the provided IP list type is not valid
|
// CheckIPListType returns an error if the provided IP list type is not valid
|
||||||
func CheckIPListType(t IPListType) error {
|
func CheckIPListType(t IPListType) error {
|
||||||
if !util.Contains(supportedIPListType, t) {
|
if !slices.Contains(supportedIPListType, t) {
|
||||||
return util.NewValidationError(fmt.Sprintf("invalid list type %d", t))
|
return util.NewValidationError(fmt.Sprintf("invalid list type %d", t))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -1210,7 +1211,7 @@ func (p *MemoryProvider) addRuleToActionMapping(ruleName, actionName string) err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.NewGenericError(fmt.Sprintf("action %q does not exist", actionName))
|
return util.NewGenericError(fmt.Sprintf("action %q does not exist", actionName))
|
||||||
}
|
}
|
||||||
if !util.Contains(a.Rules, ruleName) {
|
if !slices.Contains(a.Rules, ruleName) {
|
||||||
a.Rules = append(a.Rules, ruleName)
|
a.Rules = append(a.Rules, ruleName)
|
||||||
p.dbHandle.actions[actionName] = a
|
p.dbHandle.actions[actionName] = a
|
||||||
}
|
}
|
||||||
|
@ -1223,7 +1224,7 @@ func (p *MemoryProvider) removeRuleFromActionMapping(ruleName, actionName string
|
||||||
providerLog(logger.LevelWarn, "action %q does not exist, cannot remove from mapping", actionName)
|
providerLog(logger.LevelWarn, "action %q does not exist, cannot remove from mapping", actionName)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if util.Contains(a.Rules, ruleName) {
|
if slices.Contains(a.Rules, ruleName) {
|
||||||
var rules []string
|
var rules []string
|
||||||
for _, r := range a.Rules {
|
for _, r := range a.Rules {
|
||||||
if r != ruleName {
|
if r != ruleName {
|
||||||
|
@ -1240,7 +1241,7 @@ func (p *MemoryProvider) addAdminToGroupMapping(username, groupname string) erro
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !util.Contains(g.Admins, username) {
|
if !slices.Contains(g.Admins, username) {
|
||||||
g.Admins = append(g.Admins, username)
|
g.Admins = append(g.Admins, username)
|
||||||
p.dbHandle.groups[groupname] = g
|
p.dbHandle.groups[groupname] = g
|
||||||
}
|
}
|
||||||
|
@ -1283,7 +1284,7 @@ func (p *MemoryProvider) addUserToGroupMapping(username, groupname string) error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !util.Contains(g.Users, username) {
|
if !slices.Contains(g.Users, username) {
|
||||||
g.Users = append(g.Users, username)
|
g.Users = append(g.Users, username)
|
||||||
p.dbHandle.groups[groupname] = g
|
p.dbHandle.groups[groupname] = g
|
||||||
}
|
}
|
||||||
|
@ -1313,7 +1314,7 @@ func (p *MemoryProvider) addAdminToRole(username, role string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: role %q does not exist", ErrForeignKeyViolated, role)
|
return fmt.Errorf("%w: role %q does not exist", ErrForeignKeyViolated, role)
|
||||||
}
|
}
|
||||||
if !util.Contains(r.Admins, username) {
|
if !slices.Contains(r.Admins, username) {
|
||||||
r.Admins = append(r.Admins, username)
|
r.Admins = append(r.Admins, username)
|
||||||
p.dbHandle.roles[role] = r
|
p.dbHandle.roles[role] = r
|
||||||
}
|
}
|
||||||
|
@ -1347,7 +1348,7 @@ func (p *MemoryProvider) addUserToRole(username, role string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: role %q does not exist", ErrForeignKeyViolated, role)
|
return fmt.Errorf("%w: role %q does not exist", ErrForeignKeyViolated, role)
|
||||||
}
|
}
|
||||||
if !util.Contains(r.Users, username) {
|
if !slices.Contains(r.Users, username) {
|
||||||
r.Users = append(r.Users, username)
|
r.Users = append(r.Users, username)
|
||||||
p.dbHandle.roles[role] = r
|
p.dbHandle.roles[role] = r
|
||||||
}
|
}
|
||||||
|
@ -1378,7 +1379,7 @@ func (p *MemoryProvider) addUserToFolderMapping(username, foldername string) err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.NewGenericError(fmt.Sprintf("unable to get folder %q: %v", foldername, err))
|
return util.NewGenericError(fmt.Sprintf("unable to get folder %q: %v", foldername, err))
|
||||||
}
|
}
|
||||||
if !util.Contains(f.Users, username) {
|
if !slices.Contains(f.Users, username) {
|
||||||
f.Users = append(f.Users, username)
|
f.Users = append(f.Users, username)
|
||||||
p.dbHandle.vfolders[foldername] = f
|
p.dbHandle.vfolders[foldername] = f
|
||||||
}
|
}
|
||||||
|
@ -1390,7 +1391,7 @@ func (p *MemoryProvider) addGroupToFolderMapping(name, foldername string) error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.NewGenericError(fmt.Sprintf("unable to get folder %q: %v", foldername, err))
|
return util.NewGenericError(fmt.Sprintf("unable to get folder %q: %v", foldername, err))
|
||||||
}
|
}
|
||||||
if !util.Contains(f.Groups, name) {
|
if !slices.Contains(f.Groups, name) {
|
||||||
f.Groups = append(f.Groups, name)
|
f.Groups = append(f.Groups, name)
|
||||||
p.dbHandle.vfolders[foldername] = f
|
p.dbHandle.vfolders[foldername] = f
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -305,7 +306,7 @@ func getPGSQLConnectionString(redactedPwd bool) string {
|
||||||
if config.DisableSNI {
|
if config.DisableSNI {
|
||||||
connectionString += " sslsni=0"
|
connectionString += " sslsni=0"
|
||||||
}
|
}
|
||||||
if util.Contains(pgSQLTargetSessionAttrs, config.TargetSessionAttrs) {
|
if slices.Contains(pgSQLTargetSessionAttrs, config.TargetSessionAttrs) {
|
||||||
connectionString += fmt.Sprintf(" target_session_attrs='%s'", config.TargetSessionAttrs)
|
connectionString += fmt.Sprintf(" target_session_attrs='%s'", config.TargetSessionAttrs)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -844,20 +845,20 @@ func (u *User) HasPermissionsInside(virtualPath string) bool {
|
||||||
// HasPerm returns true if the user has the given permission or any permission
|
// HasPerm returns true if the user has the given permission or any permission
|
||||||
func (u *User) HasPerm(permission, path string) bool {
|
func (u *User) HasPerm(permission, path string) bool {
|
||||||
perms := u.GetPermissionsForPath(path)
|
perms := u.GetPermissionsForPath(path)
|
||||||
if util.Contains(perms, PermAny) {
|
if slices.Contains(perms, PermAny) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return util.Contains(perms, permission)
|
return slices.Contains(perms, permission)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasAnyPerm returns true if the user has at least one of the given permissions
|
// HasAnyPerm returns true if the user has at least one of the given permissions
|
||||||
func (u *User) HasAnyPerm(permissions []string, path string) bool {
|
func (u *User) HasAnyPerm(permissions []string, path string) bool {
|
||||||
perms := u.GetPermissionsForPath(path)
|
perms := u.GetPermissionsForPath(path)
|
||||||
if util.Contains(perms, PermAny) {
|
if slices.Contains(perms, PermAny) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, permission := range permissions {
|
for _, permission := range permissions {
|
||||||
if util.Contains(perms, permission) {
|
if slices.Contains(perms, permission) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -867,11 +868,11 @@ func (u *User) HasAnyPerm(permissions []string, path string) bool {
|
||||||
// HasPerms returns true if the user has all the given permissions
|
// HasPerms returns true if the user has all the given permissions
|
||||||
func (u *User) HasPerms(permissions []string, path string) bool {
|
func (u *User) HasPerms(permissions []string, path string) bool {
|
||||||
perms := u.GetPermissionsForPath(path)
|
perms := u.GetPermissionsForPath(path)
|
||||||
if util.Contains(perms, PermAny) {
|
if slices.Contains(perms, PermAny) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, permission := range permissions {
|
for _, permission := range permissions {
|
||||||
if !util.Contains(perms, permission) {
|
if !slices.Contains(perms, permission) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -931,11 +932,11 @@ func (u *User) IsLoginMethodAllowed(loginMethod, protocol string) bool {
|
||||||
if len(u.Filters.DeniedLoginMethods) == 0 {
|
if len(u.Filters.DeniedLoginMethods) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if util.Contains(u.Filters.DeniedLoginMethods, loginMethod) {
|
if slices.Contains(u.Filters.DeniedLoginMethods, loginMethod) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if protocol == protocolSSH && loginMethod == LoginMethodPassword {
|
if protocol == protocolSSH && loginMethod == LoginMethodPassword {
|
||||||
if util.Contains(u.Filters.DeniedLoginMethods, SSHLoginMethodPassword) {
|
if slices.Contains(u.Filters.DeniedLoginMethods, SSHLoginMethodPassword) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -969,10 +970,10 @@ func (u *User) IsPartialAuth() bool {
|
||||||
method == SSHLoginMethodPassword {
|
method == SSHLoginMethodPassword {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if method == LoginMethodPassword && util.Contains(u.Filters.DeniedLoginMethods, SSHLoginMethodPassword) {
|
if method == LoginMethodPassword && slices.Contains(u.Filters.DeniedLoginMethods, SSHLoginMethodPassword) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !util.Contains(SSHMultiStepsLoginMethods, method) {
|
if !slices.Contains(SSHMultiStepsLoginMethods, method) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -986,7 +987,7 @@ func (u *User) GetAllowedLoginMethods() []string {
|
||||||
if method == SSHLoginMethodPassword {
|
if method == SSHLoginMethodPassword {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !util.Contains(u.Filters.DeniedLoginMethods, method) {
|
if !slices.Contains(u.Filters.DeniedLoginMethods, method) {
|
||||||
allowedMethods = append(allowedMethods, method)
|
allowedMethods = append(allowedMethods, method)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1056,7 +1057,7 @@ func (u *User) IsFileAllowed(virtualPath string) (bool, int) {
|
||||||
|
|
||||||
// CanManageMFA returns true if the user can add a multi-factor authentication configuration
|
// CanManageMFA returns true if the user can add a multi-factor authentication configuration
|
||||||
func (u *User) CanManageMFA() bool {
|
func (u *User) CanManageMFA() bool {
|
||||||
if util.Contains(u.Filters.WebClient, sdk.WebClientMFADisabled) {
|
if slices.Contains(u.Filters.WebClient, sdk.WebClientMFADisabled) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return len(mfa.GetAvailableTOTPConfigs()) > 0
|
return len(mfa.GetAvailableTOTPConfigs()) > 0
|
||||||
|
@ -1077,39 +1078,39 @@ func (u *User) skipExternalAuth() bool {
|
||||||
|
|
||||||
// CanManageShares returns true if the user can add, update and list shares
|
// CanManageShares returns true if the user can add, update and list shares
|
||||||
func (u *User) CanManageShares() bool {
|
func (u *User) CanManageShares() bool {
|
||||||
return !util.Contains(u.Filters.WebClient, sdk.WebClientSharesDisabled)
|
return !slices.Contains(u.Filters.WebClient, sdk.WebClientSharesDisabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanResetPassword returns true if this user is allowed to reset its password
|
// CanResetPassword returns true if this user is allowed to reset its password
|
||||||
func (u *User) CanResetPassword() bool {
|
func (u *User) CanResetPassword() bool {
|
||||||
return !util.Contains(u.Filters.WebClient, sdk.WebClientPasswordResetDisabled)
|
return !slices.Contains(u.Filters.WebClient, sdk.WebClientPasswordResetDisabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanChangePassword returns true if this user is allowed to change its password
|
// CanChangePassword returns true if this user is allowed to change its password
|
||||||
func (u *User) CanChangePassword() bool {
|
func (u *User) CanChangePassword() bool {
|
||||||
return !util.Contains(u.Filters.WebClient, sdk.WebClientPasswordChangeDisabled)
|
return !slices.Contains(u.Filters.WebClient, sdk.WebClientPasswordChangeDisabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanChangeAPIKeyAuth returns true if this user is allowed to enable/disable API key authentication
|
// CanChangeAPIKeyAuth returns true if this user is allowed to enable/disable API key authentication
|
||||||
func (u *User) CanChangeAPIKeyAuth() bool {
|
func (u *User) CanChangeAPIKeyAuth() bool {
|
||||||
return !util.Contains(u.Filters.WebClient, sdk.WebClientAPIKeyAuthChangeDisabled)
|
return !slices.Contains(u.Filters.WebClient, sdk.WebClientAPIKeyAuthChangeDisabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanChangeInfo returns true if this user is allowed to change its info such as email and description
|
// CanChangeInfo returns true if this user is allowed to change its info such as email and description
|
||||||
func (u *User) CanChangeInfo() bool {
|
func (u *User) CanChangeInfo() bool {
|
||||||
return !util.Contains(u.Filters.WebClient, sdk.WebClientInfoChangeDisabled)
|
return !slices.Contains(u.Filters.WebClient, sdk.WebClientInfoChangeDisabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanManagePublicKeys returns true if this user is allowed to manage public keys
|
// CanManagePublicKeys returns true if this user is allowed to manage public keys
|
||||||
// from the WebClient. Used in WebClient UI
|
// from the WebClient. Used in WebClient UI
|
||||||
func (u *User) CanManagePublicKeys() bool {
|
func (u *User) CanManagePublicKeys() bool {
|
||||||
return !util.Contains(u.Filters.WebClient, sdk.WebClientPubKeyChangeDisabled)
|
return !slices.Contains(u.Filters.WebClient, sdk.WebClientPubKeyChangeDisabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanManageTLSCerts returns true if this user is allowed to manage TLS certificates
|
// CanManageTLSCerts returns true if this user is allowed to manage TLS certificates
|
||||||
// from the WebClient. Used in WebClient UI
|
// from the WebClient. Used in WebClient UI
|
||||||
func (u *User) CanManageTLSCerts() bool {
|
func (u *User) CanManageTLSCerts() bool {
|
||||||
return !util.Contains(u.Filters.WebClient, sdk.WebClientTLSCertChangeDisabled)
|
return !slices.Contains(u.Filters.WebClient, sdk.WebClientTLSCertChangeDisabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanUpdateProfile returns true if the user is allowed to update the profile.
|
// CanUpdateProfile returns true if the user is allowed to update the profile.
|
||||||
|
@ -1121,7 +1122,7 @@ func (u *User) CanUpdateProfile() bool {
|
||||||
// CanAddFilesFromWeb returns true if the client can add files from the web UI.
|
// CanAddFilesFromWeb returns true if the client can add files from the web UI.
|
||||||
// The specified target is the directory where the files must be uploaded
|
// The specified target is the directory where the files must be uploaded
|
||||||
func (u *User) CanAddFilesFromWeb(target string) bool {
|
func (u *User) CanAddFilesFromWeb(target string) bool {
|
||||||
if util.Contains(u.Filters.WebClient, sdk.WebClientWriteDisabled) {
|
if slices.Contains(u.Filters.WebClient, sdk.WebClientWriteDisabled) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return u.HasPerm(PermUpload, target) || u.HasPerm(PermOverwrite, target)
|
return u.HasPerm(PermUpload, target) || u.HasPerm(PermOverwrite, target)
|
||||||
|
@ -1130,7 +1131,7 @@ func (u *User) CanAddFilesFromWeb(target string) bool {
|
||||||
// CanAddDirsFromWeb returns true if the client can add directories from the web UI.
|
// CanAddDirsFromWeb returns true if the client can add directories from the web UI.
|
||||||
// The specified target is the directory where the new directory must be created
|
// The specified target is the directory where the new directory must be created
|
||||||
func (u *User) CanAddDirsFromWeb(target string) bool {
|
func (u *User) CanAddDirsFromWeb(target string) bool {
|
||||||
if util.Contains(u.Filters.WebClient, sdk.WebClientWriteDisabled) {
|
if slices.Contains(u.Filters.WebClient, sdk.WebClientWriteDisabled) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return u.HasPerm(PermCreateDirs, target)
|
return u.HasPerm(PermCreateDirs, target)
|
||||||
|
@ -1139,7 +1140,7 @@ func (u *User) CanAddDirsFromWeb(target string) bool {
|
||||||
// CanRenameFromWeb returns true if the client can rename objects from the web UI.
|
// CanRenameFromWeb returns true if the client can rename objects from the web UI.
|
||||||
// The specified src and dest are the source and target directories for the rename.
|
// The specified src and dest are the source and target directories for the rename.
|
||||||
func (u *User) CanRenameFromWeb(src, dest string) bool {
|
func (u *User) CanRenameFromWeb(src, dest string) bool {
|
||||||
if util.Contains(u.Filters.WebClient, sdk.WebClientWriteDisabled) {
|
if slices.Contains(u.Filters.WebClient, sdk.WebClientWriteDisabled) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return u.HasAnyPerm(permsRenameAny, src) && u.HasAnyPerm(permsRenameAny, dest)
|
return u.HasAnyPerm(permsRenameAny, src) && u.HasAnyPerm(permsRenameAny, dest)
|
||||||
|
@ -1148,7 +1149,7 @@ func (u *User) CanRenameFromWeb(src, dest string) bool {
|
||||||
// CanDeleteFromWeb returns true if the client can delete objects from the web UI.
|
// CanDeleteFromWeb returns true if the client can delete objects from the web UI.
|
||||||
// The specified target is the parent directory for the object to delete
|
// The specified target is the parent directory for the object to delete
|
||||||
func (u *User) CanDeleteFromWeb(target string) bool {
|
func (u *User) CanDeleteFromWeb(target string) bool {
|
||||||
if util.Contains(u.Filters.WebClient, sdk.WebClientWriteDisabled) {
|
if slices.Contains(u.Filters.WebClient, sdk.WebClientWriteDisabled) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return u.HasAnyPerm(permsDeleteAny, target)
|
return u.HasAnyPerm(permsDeleteAny, target)
|
||||||
|
@ -1157,7 +1158,7 @@ func (u *User) CanDeleteFromWeb(target string) bool {
|
||||||
// CanCopyFromWeb returns true if the client can copy objects from the web UI.
|
// CanCopyFromWeb returns true if the client can copy objects from the web UI.
|
||||||
// The specified src and dest are the source and target directories for the copy.
|
// The specified src and dest are the source and target directories for the copy.
|
||||||
func (u *User) CanCopyFromWeb(src, dest string) bool {
|
func (u *User) CanCopyFromWeb(src, dest string) bool {
|
||||||
if util.Contains(u.Filters.WebClient, sdk.WebClientWriteDisabled) {
|
if slices.Contains(u.Filters.WebClient, sdk.WebClientWriteDisabled) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !u.HasPerm(PermListItems, src) {
|
if !u.HasPerm(PermListItems, src) {
|
||||||
|
@ -1217,7 +1218,7 @@ func (u *User) MustSetSecondFactor() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, p := range u.Filters.TwoFactorAuthProtocols {
|
for _, p := range u.Filters.TwoFactorAuthProtocols {
|
||||||
if !util.Contains(u.Filters.TOTPConfig.Protocols, p) {
|
if !slices.Contains(u.Filters.TOTPConfig.Protocols, p) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1228,11 +1229,11 @@ func (u *User) MustSetSecondFactor() bool {
|
||||||
// MustSetSecondFactorForProtocol returns true if the user must set a second factor authentication
|
// MustSetSecondFactorForProtocol returns true if the user must set a second factor authentication
|
||||||
// for the specified protocol
|
// for the specified protocol
|
||||||
func (u *User) MustSetSecondFactorForProtocol(protocol string) bool {
|
func (u *User) MustSetSecondFactorForProtocol(protocol string) bool {
|
||||||
if util.Contains(u.Filters.TwoFactorAuthProtocols, protocol) {
|
if slices.Contains(u.Filters.TwoFactorAuthProtocols, protocol) {
|
||||||
if !u.Filters.TOTPConfig.Enabled {
|
if !u.Filters.TOTPConfig.Enabled {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if !util.Contains(u.Filters.TOTPConfig.Protocols, protocol) {
|
if !slices.Contains(u.Filters.TOTPConfig.Protocols, protocol) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
|
||||||
ftpserver "github.com/fclairamb/ftpserverlib"
|
ftpserver "github.com/fclairamb/ftpserverlib"
|
||||||
"github.com/sftpgo/sdk/plugin/notifier"
|
"github.com/sftpgo/sdk/plugin/notifier"
|
||||||
|
@ -361,7 +362,7 @@ func (s *Server) validateUser(user dataprovider.User, cc ftpserver.ClientContext
|
||||||
user.Username, user.HomeDir)
|
user.Username, user.HomeDir)
|
||||||
return nil, fmt.Errorf("cannot login user with invalid home dir: %q", user.HomeDir)
|
return nil, fmt.Errorf("cannot login user with invalid home dir: %q", user.HomeDir)
|
||||||
}
|
}
|
||||||
if util.Contains(user.Filters.DeniedProtocols, common.ProtocolFTP) {
|
if slices.Contains(user.Filters.DeniedProtocols, common.ProtocolFTP) {
|
||||||
logger.Info(logSender, connectionID, "cannot login user %q, protocol FTP is not allowed", user.Username)
|
logger.Info(logSender, connectionID, "cannot login user %q, protocol FTP is not allowed", user.Username)
|
||||||
return nil, fmt.Errorf("protocol FTP is not allowed for user %q", user.Username)
|
return nil, fmt.Errorf("protocol FTP is not allowed for user %q", user.Username)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -275,7 +276,7 @@ func saveUserTOTPConfig(username string, r *http.Request, recoveryCodes []datapr
|
||||||
return util.NewValidationError("two-factor authentication must be enabled")
|
return util.NewValidationError("two-factor authentication must be enabled")
|
||||||
}
|
}
|
||||||
for _, p := range userMerged.Filters.TwoFactorAuthProtocols {
|
for _, p := range userMerged.Filters.TwoFactorAuthProtocols {
|
||||||
if !util.Contains(user.Filters.TOTPConfig.Protocols, p) {
|
if !slices.Contains(user.Filters.TOTPConfig.Protocols, p) {
|
||||||
return util.NewValidationError(fmt.Sprintf("totp: the following protocols are required: %q",
|
return util.NewValidationError(fmt.Sprintf("totp: the following protocols are required: %q",
|
||||||
strings.Join(userMerged.Filters.TwoFactorAuthProtocols, ", ")))
|
strings.Join(userMerged.Filters.TwoFactorAuthProtocols, ", ")))
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -107,7 +108,7 @@ func addShare(w http.ResponseWriter, r *http.Request) {
|
||||||
share.Name = share.ShareID
|
share.Name = share.ShareID
|
||||||
}
|
}
|
||||||
if share.Password == "" {
|
if share.Password == "" {
|
||||||
if util.Contains(claims.Permissions, sdk.WebClientShareNoPasswordDisabled) {
|
if slices.Contains(claims.Permissions, sdk.WebClientShareNoPasswordDisabled) {
|
||||||
sendAPIResponse(w, r, nil, "You are not authorized to share files/folders without a password",
|
sendAPIResponse(w, r, nil, "You are not authorized to share files/folders without a password",
|
||||||
http.StatusForbidden)
|
http.StatusForbidden)
|
||||||
return
|
return
|
||||||
|
@ -155,7 +156,7 @@ func updateShare(w http.ResponseWriter, r *http.Request) {
|
||||||
updatedShare.Password = share.Password
|
updatedShare.Password = share.Password
|
||||||
}
|
}
|
||||||
if updatedShare.Password == "" {
|
if updatedShare.Password == "" {
|
||||||
if util.Contains(claims.Permissions, sdk.WebClientShareNoPasswordDisabled) {
|
if slices.Contains(claims.Permissions, sdk.WebClientShareNoPasswordDisabled) {
|
||||||
sendAPIResponse(w, r, nil, "You are not authorized to share files/folders without a password",
|
sendAPIResponse(w, r, nil, "You are not authorized to share files/folders without a password",
|
||||||
http.StatusForbidden)
|
http.StatusForbidden)
|
||||||
return
|
return
|
||||||
|
@ -434,7 +435,7 @@ func (s *httpdServer) getShareClaims(r *http.Request, shareID string) (*jwtToken
|
||||||
if tokenString == "" || invalidatedJWTTokens.Get(tokenString) {
|
if tokenString == "" || invalidatedJWTTokens.Get(tokenString) {
|
||||||
return nil, errInvalidToken
|
return nil, errInvalidToken
|
||||||
}
|
}
|
||||||
if !util.Contains(token.Audience(), tokenAudienceWebShare) {
|
if !slices.Contains(token.Audience(), tokenAudienceWebShare) {
|
||||||
logger.Debug(logSender, "", "invalid token audience for share %q", shareID)
|
logger.Debug(logSender, "", "invalid token audience for share %q", shareID)
|
||||||
return nil, errInvalidToken
|
return nil, errInvalidToken
|
||||||
}
|
}
|
||||||
|
@ -486,7 +487,7 @@ func (s *httpdServer) checkPublicShare(w http.ResponseWriter, r *http.Request, v
|
||||||
renderError(err, "", statusCode)
|
renderError(err, "", statusCode)
|
||||||
return share, nil, err
|
return share, nil, err
|
||||||
}
|
}
|
||||||
if !util.Contains(validScopes, share.Scope) {
|
if !slices.Contains(validScopes, share.Scope) {
|
||||||
err := errors.New("invalid share scope")
|
err := errors.New("invalid share scope")
|
||||||
renderError(util.NewI18nError(err, util.I18nErrorShareScope), "", http.StatusForbidden)
|
renderError(util.NewI18nError(err, util.I18nErrorShareScope), "", http.StatusForbidden)
|
||||||
return share, nil, err
|
return share, nil, err
|
||||||
|
@ -543,7 +544,7 @@ func getUserForShare(share dataprovider.Share) (dataprovider.User, error) {
|
||||||
if !user.CanManageShares() {
|
if !user.CanManageShares() {
|
||||||
return user, util.NewI18nError(util.NewRecordNotFoundError("this share does not exist"), util.I18nError404Message)
|
return user, util.NewI18nError(util.NewRecordNotFoundError("this share does not exist"), util.I18nError404Message)
|
||||||
}
|
}
|
||||||
if share.Password == "" && util.Contains(user.Filters.WebClient, sdk.WebClientShareNoPasswordDisabled) {
|
if share.Password == "" && slices.Contains(user.Filters.WebClient, sdk.WebClientShareNoPasswordDisabled) {
|
||||||
return user, util.NewI18nError(
|
return user, util.NewI18nError(
|
||||||
fmt.Errorf("sharing without a password was disabled: %w", os.ErrPermission),
|
fmt.Errorf("sharing without a password was disabled: %w", os.ErrPermission),
|
||||||
util.I18nError403Message,
|
util.I18nError403Message,
|
||||||
|
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -727,7 +728,7 @@ func updateLoginMetrics(user *dataprovider.User, loginMethod, ip string, err err
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkHTTPClientUser(user *dataprovider.User, r *http.Request, connectionID string, checkSessions bool) error {
|
func checkHTTPClientUser(user *dataprovider.User, r *http.Request, connectionID string, checkSessions bool) error {
|
||||||
if util.Contains(user.Filters.DeniedProtocols, common.ProtocolHTTP) {
|
if slices.Contains(user.Filters.DeniedProtocols, common.ProtocolHTTP) {
|
||||||
logger.Info(logSender, connectionID, "cannot login user %q, protocol HTTP is not allowed", user.Username)
|
logger.Info(logSender, connectionID, "cannot login user %q, protocol HTTP is not allowed", user.Username)
|
||||||
return util.NewI18nError(
|
return util.NewI18nError(
|
||||||
fmt.Errorf("protocol HTTP is not allowed for user %q", user.Username),
|
fmt.Errorf("protocol HTTP is not allowed for user %q", user.Username),
|
||||||
|
@ -912,7 +913,7 @@ func isUserAllowedToResetPassword(r *http.Request, user *dataprovider.User) bool
|
||||||
if !user.CanResetPassword() {
|
if !user.CanResetPassword() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if util.Contains(user.Filters.DeniedProtocols, common.ProtocolHTTP) {
|
if slices.Contains(user.Filters.DeniedProtocols, common.ProtocolHTTP) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !user.IsLoginMethodAllowed(dataprovider.LoginMethodPassword, common.ProtocolHTTP) {
|
if !user.IsLoginMethodAllowed(dataprovider.LoginMethodPassword, common.ProtocolHTTP) {
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-chi/jwtauth/v5"
|
"github.com/go-chi/jwtauth/v5"
|
||||||
|
@ -227,24 +228,24 @@ func (c *jwtTokenClaims) Decode(token map[string]any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *jwtTokenClaims) isCriticalPermRemoved(permissions []string) bool {
|
func (c *jwtTokenClaims) isCriticalPermRemoved(permissions []string) bool {
|
||||||
if util.Contains(permissions, dataprovider.PermAdminAny) {
|
if slices.Contains(permissions, dataprovider.PermAdminAny) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (util.Contains(c.Permissions, dataprovider.PermAdminManageAdmins) ||
|
if (slices.Contains(c.Permissions, dataprovider.PermAdminManageAdmins) ||
|
||||||
util.Contains(c.Permissions, dataprovider.PermAdminAny)) &&
|
slices.Contains(c.Permissions, dataprovider.PermAdminAny)) &&
|
||||||
!util.Contains(permissions, dataprovider.PermAdminManageAdmins) &&
|
!slices.Contains(permissions, dataprovider.PermAdminManageAdmins) &&
|
||||||
!util.Contains(permissions, dataprovider.PermAdminAny) {
|
!slices.Contains(permissions, dataprovider.PermAdminAny) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *jwtTokenClaims) hasPerm(perm string) bool {
|
func (c *jwtTokenClaims) hasPerm(perm string) bool {
|
||||||
if util.Contains(c.Permissions, dataprovider.PermAdminAny) {
|
if slices.Contains(c.Permissions, dataprovider.PermAdminAny) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return util.Contains(c.Permissions, perm)
|
return slices.Contains(c.Permissions, perm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *jwtTokenClaims) createToken(tokenAuth *jwtauth.JWTAuth, audience tokenAudience, ip string) (jwt.Token, string, error) {
|
func (c *jwtTokenClaims) createToken(tokenAuth *jwtauth.JWTAuth, audience tokenAudience, ip string) (jwt.Token, string, error) {
|
||||||
|
@ -458,7 +459,7 @@ func verifyCSRFToken(r *http.Request, csrfTokenAuth *jwtauth.JWTAuth) error {
|
||||||
return fmt.Errorf("unable to verify form token: %v", err)
|
return fmt.Errorf("unable to verify form token: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !util.Contains(token.Audience(), tokenAudienceCSRF) {
|
if !slices.Contains(token.Audience(), tokenAudienceCSRF) {
|
||||||
logger.Debug(logSender, "", "error validating CSRF token audience")
|
logger.Debug(logSender, "", "error validating CSRF token audience")
|
||||||
return errors.New("the form token is not valid")
|
return errors.New("the form token is not valid")
|
||||||
}
|
}
|
||||||
|
@ -495,7 +496,7 @@ func verifyLoginCookie(r *http.Request) error {
|
||||||
logger.Debug(logSender, "", "the login token has been invalidated")
|
logger.Debug(logSender, "", "the login token has been invalidated")
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
if !util.Contains(token.Audience(), tokenAudienceWebLogin) {
|
if !slices.Contains(token.Audience(), tokenAudienceWebLogin) {
|
||||||
logger.Debug(logSender, "", "the token with id %q is not valid for audience %q", token.JwtID(), tokenAudienceWebLogin)
|
logger.Debug(logSender, "", "the token with id %q is not valid for audience %q", token.JwtID(), tokenAudienceWebLogin)
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
|
@ -543,7 +544,7 @@ func verifyOAuth2Token(csrfTokenAuth *jwtauth.JWTAuth, tokenString, ip string) (
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !util.Contains(token.Audience(), tokenAudienceOAuth2) {
|
if !slices.Contains(token.Audience(), tokenAudienceOAuth2) {
|
||||||
logger.Debug(logSender, "", "error validating OAuth2 token audience")
|
logger.Debug(logSender, "", "error validating OAuth2 token audience")
|
||||||
return "", util.NewI18nError(errors.New("invalid OAuth2 state"), util.I18nOAuth2InvalidState)
|
return "", util.NewI18nError(errors.New("invalid OAuth2 state"), util.I18nOAuth2InvalidState)
|
||||||
}
|
}
|
||||||
|
@ -563,7 +564,7 @@ func verifyOAuth2Token(csrfTokenAuth *jwtauth.JWTAuth, tokenString, ip string) (
|
||||||
|
|
||||||
func validateIPForToken(token jwt.Token, ip string) error {
|
func validateIPForToken(token jwt.Token, ip string) error {
|
||||||
if tokenValidationMode != tokenValidationNoIPMatch {
|
if tokenValidationMode != tokenValidationNoIPMatch {
|
||||||
if !util.Contains(token.Audience(), ip) {
|
if !slices.Contains(token.Audience(), ip) {
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -1340,7 +1341,7 @@ func TestGroupSettingsOverride(t *testing.T) {
|
||||||
var folderNames []string
|
var folderNames []string
|
||||||
if assert.Len(t, user.VirtualFolders, 4) {
|
if assert.Len(t, user.VirtualFolders, 4) {
|
||||||
for _, f := range user.VirtualFolders {
|
for _, f := range user.VirtualFolders {
|
||||||
if !util.Contains(folderNames, f.Name) {
|
if !slices.Contains(folderNames, f.Name) {
|
||||||
folderNames = append(folderNames, f.Name)
|
folderNames = append(folderNames, f.Name)
|
||||||
}
|
}
|
||||||
switch f.Name {
|
switch f.Name {
|
||||||
|
@ -1348,7 +1349,7 @@ func TestGroupSettingsOverride(t *testing.T) {
|
||||||
assert.Equal(t, mappedPath1, f.MappedPath)
|
assert.Equal(t, mappedPath1, f.MappedPath)
|
||||||
assert.Equal(t, 3, f.BaseVirtualFolder.FsConfig.OSConfig.ReadBufferSize)
|
assert.Equal(t, 3, f.BaseVirtualFolder.FsConfig.OSConfig.ReadBufferSize)
|
||||||
assert.Equal(t, 5, f.BaseVirtualFolder.FsConfig.OSConfig.WriteBufferSize)
|
assert.Equal(t, 5, f.BaseVirtualFolder.FsConfig.OSConfig.WriteBufferSize)
|
||||||
assert.True(t, util.Contains([]string{"/vdir1", "/vdir2"}, f.VirtualPath))
|
assert.True(t, slices.Contains([]string{"/vdir1", "/vdir2"}, f.VirtualPath))
|
||||||
case folderName2:
|
case folderName2:
|
||||||
assert.Equal(t, mappedPath2, f.MappedPath)
|
assert.Equal(t, mappedPath2, f.MappedPath)
|
||||||
assert.Equal(t, "/vdir3", f.VirtualPath)
|
assert.Equal(t, "/vdir3", f.VirtualPath)
|
||||||
|
@ -2103,16 +2104,16 @@ func TestActionRuleRelations(t *testing.T) {
|
||||||
action1, _, err = httpdtest.GetEventActionByName(action1.Name, http.StatusOK)
|
action1, _, err = httpdtest.GetEventActionByName(action1.Name, http.StatusOK)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, action1.Rules, 1)
|
assert.Len(t, action1.Rules, 1)
|
||||||
assert.True(t, util.Contains(action1.Rules, rule1.Name))
|
assert.True(t, slices.Contains(action1.Rules, rule1.Name))
|
||||||
action2, _, err = httpdtest.GetEventActionByName(action2.Name, http.StatusOK)
|
action2, _, err = httpdtest.GetEventActionByName(action2.Name, http.StatusOK)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, action2.Rules, 1)
|
assert.Len(t, action2.Rules, 1)
|
||||||
assert.True(t, util.Contains(action2.Rules, rule2.Name))
|
assert.True(t, slices.Contains(action2.Rules, rule2.Name))
|
||||||
action3, _, err = httpdtest.GetEventActionByName(action3.Name, http.StatusOK)
|
action3, _, err = httpdtest.GetEventActionByName(action3.Name, http.StatusOK)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, action3.Rules, 2)
|
assert.Len(t, action3.Rules, 2)
|
||||||
assert.True(t, util.Contains(action3.Rules, rule1.Name))
|
assert.True(t, slices.Contains(action3.Rules, rule1.Name))
|
||||||
assert.True(t, util.Contains(action3.Rules, rule2.Name))
|
assert.True(t, slices.Contains(action3.Rules, rule2.Name))
|
||||||
// referenced actions cannot be removed
|
// referenced actions cannot be removed
|
||||||
_, err = httpdtest.RemoveEventAction(action1, http.StatusBadRequest)
|
_, err = httpdtest.RemoveEventAction(action1, http.StatusBadRequest)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -2140,7 +2141,7 @@ func TestActionRuleRelations(t *testing.T) {
|
||||||
action3, _, err = httpdtest.GetEventActionByName(action3.Name, http.StatusOK)
|
action3, _, err = httpdtest.GetEventActionByName(action3.Name, http.StatusOK)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, action3.Rules, 1)
|
assert.Len(t, action3.Rules, 1)
|
||||||
assert.True(t, util.Contains(action3.Rules, rule1.Name))
|
assert.True(t, slices.Contains(action3.Rules, rule1.Name))
|
||||||
|
|
||||||
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -8912,7 +8913,7 @@ func TestBasicUserHandlingMock(t *testing.T) {
|
||||||
assert.Equal(t, user.MaxSessions, updatedUser.MaxSessions)
|
assert.Equal(t, user.MaxSessions, updatedUser.MaxSessions)
|
||||||
assert.Equal(t, user.UploadBandwidth, updatedUser.UploadBandwidth)
|
assert.Equal(t, user.UploadBandwidth, updatedUser.UploadBandwidth)
|
||||||
assert.Equal(t, 1, len(updatedUser.Permissions["/"]))
|
assert.Equal(t, 1, len(updatedUser.Permissions["/"]))
|
||||||
assert.True(t, util.Contains(updatedUser.Permissions["/"], dataprovider.PermAny))
|
assert.True(t, slices.Contains(updatedUser.Permissions["/"], dataprovider.PermAny))
|
||||||
req, _ = http.NewRequest(http.MethodDelete, userPath+"/"+user.Username, nil)
|
req, _ = http.NewRequest(http.MethodDelete, userPath+"/"+user.Username, nil)
|
||||||
setBearerForReq(req, token)
|
setBearerForReq(req, token)
|
||||||
rr = executeRequest(req)
|
rr = executeRequest(req)
|
||||||
|
@ -12140,7 +12141,7 @@ func TestUserPermissionsMock(t *testing.T) {
|
||||||
err = render.DecodeJSON(rr.Body, &updatedUser)
|
err = render.DecodeJSON(rr.Body, &updatedUser)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
if val, ok := updatedUser.Permissions["/otherdir"]; ok {
|
if val, ok := updatedUser.Permissions["/otherdir"]; ok {
|
||||||
assert.True(t, util.Contains(val, dataprovider.PermListItems))
|
assert.True(t, slices.Contains(val, dataprovider.PermListItems))
|
||||||
assert.Equal(t, 1, len(val))
|
assert.Equal(t, 1, len(val))
|
||||||
} else {
|
} else {
|
||||||
assert.Fail(t, "expected dir not found in permissions")
|
assert.Fail(t, "expected dir not found in permissions")
|
||||||
|
@ -21552,10 +21553,10 @@ func TestWebUserAddMock(t *testing.T) {
|
||||||
assert.Equal(t, 60, newUser.Filters.PasswordStrength)
|
assert.Equal(t, 60, newUser.Filters.PasswordStrength)
|
||||||
assert.Greater(t, newUser.LastPasswordChange, int64(0))
|
assert.Greater(t, newUser.LastPasswordChange, int64(0))
|
||||||
assert.True(t, newUser.Filters.RequirePasswordChange)
|
assert.True(t, newUser.Filters.RequirePasswordChange)
|
||||||
assert.True(t, util.Contains(newUser.PublicKeys, testPubKey))
|
assert.True(t, slices.Contains(newUser.PublicKeys, testPubKey))
|
||||||
if val, ok := newUser.Permissions["/subdir"]; ok {
|
if val, ok := newUser.Permissions["/subdir"]; ok {
|
||||||
assert.True(t, util.Contains(val, dataprovider.PermListItems))
|
assert.True(t, slices.Contains(val, dataprovider.PermListItems))
|
||||||
assert.True(t, util.Contains(val, dataprovider.PermDownload))
|
assert.True(t, slices.Contains(val, dataprovider.PermDownload))
|
||||||
} else {
|
} else {
|
||||||
assert.Fail(t, "user permissions must contain /somedir", "actual: %v", newUser.Permissions)
|
assert.Fail(t, "user permissions must contain /somedir", "actual: %v", newUser.Permissions)
|
||||||
}
|
}
|
||||||
|
@ -21574,20 +21575,20 @@ func TestWebUserAddMock(t *testing.T) {
|
||||||
case "/dir1":
|
case "/dir1":
|
||||||
assert.Len(t, filter.DeniedPatterns, 1)
|
assert.Len(t, filter.DeniedPatterns, 1)
|
||||||
assert.Len(t, filter.AllowedPatterns, 1)
|
assert.Len(t, filter.AllowedPatterns, 1)
|
||||||
assert.True(t, util.Contains(filter.AllowedPatterns, "*.png"))
|
assert.True(t, slices.Contains(filter.AllowedPatterns, "*.png"))
|
||||||
assert.True(t, util.Contains(filter.DeniedPatterns, "*.zip"))
|
assert.True(t, slices.Contains(filter.DeniedPatterns, "*.zip"))
|
||||||
assert.Equal(t, sdk.DenyPolicyDefault, filter.DenyPolicy)
|
assert.Equal(t, sdk.DenyPolicyDefault, filter.DenyPolicy)
|
||||||
case "/dir2":
|
case "/dir2":
|
||||||
assert.Len(t, filter.DeniedPatterns, 1)
|
assert.Len(t, filter.DeniedPatterns, 1)
|
||||||
assert.Len(t, filter.AllowedPatterns, 2)
|
assert.Len(t, filter.AllowedPatterns, 2)
|
||||||
assert.True(t, util.Contains(filter.AllowedPatterns, "*.jpg"))
|
assert.True(t, slices.Contains(filter.AllowedPatterns, "*.jpg"))
|
||||||
assert.True(t, util.Contains(filter.AllowedPatterns, "*.png"))
|
assert.True(t, slices.Contains(filter.AllowedPatterns, "*.png"))
|
||||||
assert.True(t, util.Contains(filter.DeniedPatterns, "*.mkv"))
|
assert.True(t, slices.Contains(filter.DeniedPatterns, "*.mkv"))
|
||||||
assert.Equal(t, sdk.DenyPolicyHide, filter.DenyPolicy)
|
assert.Equal(t, sdk.DenyPolicyHide, filter.DenyPolicy)
|
||||||
case "/dir3":
|
case "/dir3":
|
||||||
assert.Len(t, filter.DeniedPatterns, 1)
|
assert.Len(t, filter.DeniedPatterns, 1)
|
||||||
assert.Len(t, filter.AllowedPatterns, 0)
|
assert.Len(t, filter.AllowedPatterns, 0)
|
||||||
assert.True(t, util.Contains(filter.DeniedPatterns, "*.rar"))
|
assert.True(t, slices.Contains(filter.DeniedPatterns, "*.rar"))
|
||||||
assert.Equal(t, sdk.DenyPolicyDefault, filter.DenyPolicy)
|
assert.Equal(t, sdk.DenyPolicyDefault, filter.DenyPolicy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21828,16 +21829,16 @@ func TestWebUserUpdateMock(t *testing.T) {
|
||||||
assert.Equal(t, 40, updateUser.Filters.PasswordStrength)
|
assert.Equal(t, 40, updateUser.Filters.PasswordStrength)
|
||||||
assert.True(t, updateUser.Filters.RequirePasswordChange)
|
assert.True(t, updateUser.Filters.RequirePasswordChange)
|
||||||
if val, ok := updateUser.Permissions["/otherdir"]; ok {
|
if val, ok := updateUser.Permissions["/otherdir"]; ok {
|
||||||
assert.True(t, util.Contains(val, dataprovider.PermListItems))
|
assert.True(t, slices.Contains(val, dataprovider.PermListItems))
|
||||||
assert.True(t, util.Contains(val, dataprovider.PermUpload))
|
assert.True(t, slices.Contains(val, dataprovider.PermUpload))
|
||||||
} else {
|
} else {
|
||||||
assert.Fail(t, "user permissions must contains /otherdir", "actual: %v", updateUser.Permissions)
|
assert.Fail(t, "user permissions must contains /otherdir", "actual: %v", updateUser.Permissions)
|
||||||
}
|
}
|
||||||
assert.True(t, util.Contains(updateUser.Filters.AllowedIP, "192.168.1.3/32"))
|
assert.True(t, slices.Contains(updateUser.Filters.AllowedIP, "192.168.1.3/32"))
|
||||||
assert.True(t, util.Contains(updateUser.Filters.DeniedIP, "10.0.0.2/32"))
|
assert.True(t, slices.Contains(updateUser.Filters.DeniedIP, "10.0.0.2/32"))
|
||||||
assert.True(t, util.Contains(updateUser.Filters.DeniedLoginMethods, dataprovider.SSHLoginMethodKeyboardInteractive))
|
assert.True(t, slices.Contains(updateUser.Filters.DeniedLoginMethods, dataprovider.SSHLoginMethodKeyboardInteractive))
|
||||||
assert.True(t, util.Contains(updateUser.Filters.DeniedProtocols, common.ProtocolFTP))
|
assert.True(t, slices.Contains(updateUser.Filters.DeniedProtocols, common.ProtocolFTP))
|
||||||
assert.True(t, util.Contains(updateUser.Filters.FilePatterns[0].DeniedPatterns, "*.zip"))
|
assert.True(t, slices.Contains(updateUser.Filters.FilePatterns[0].DeniedPatterns, "*.zip"))
|
||||||
assert.Len(t, updateUser.Filters.BandwidthLimits, 0)
|
assert.Len(t, updateUser.Filters.BandwidthLimits, 0)
|
||||||
assert.Len(t, updateUser.Filters.TLSCerts, 1)
|
assert.Len(t, updateUser.Filters.TLSCerts, 1)
|
||||||
req, err = http.NewRequest(http.MethodDelete, path.Join(userPath, user.Username), nil)
|
req, err = http.NewRequest(http.MethodDelete, path.Join(userPath, user.Username), nil)
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-chi/jwtauth/v5"
|
"github.com/go-chi/jwtauth/v5"
|
||||||
|
@ -83,7 +84,7 @@ func validateJWTToken(w http.ResponseWriter, r *http.Request, audience tokenAudi
|
||||||
if err := checkPartialAuth(w, r, audience, token.Audience()); err != nil {
|
if err := checkPartialAuth(w, r, audience, token.Audience()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !util.Contains(token.Audience(), audience) {
|
if !slices.Contains(token.Audience(), audience) {
|
||||||
logger.Debug(logSender, "", "the token is not valid for audience %q", audience)
|
logger.Debug(logSender, "", "the token is not valid for audience %q", audience)
|
||||||
doRedirect("Your token audience is not valid", nil)
|
doRedirect("Your token audience is not valid", nil)
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
|
@ -113,7 +114,7 @@ func (s *httpdServer) validateJWTPartialToken(w http.ResponseWriter, r *http.Req
|
||||||
notFoundFunc(w, r, nil)
|
notFoundFunc(w, r, nil)
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
if !util.Contains(token.Audience(), audience) {
|
if !slices.Contains(token.Audience(), audience) {
|
||||||
logger.Debug(logSender, "", "the partial token with id %q is not valid for audience %q", token.JwtID(), audience)
|
logger.Debug(logSender, "", "the partial token with id %q is not valid for audience %q", token.JwtID(), audience)
|
||||||
notFoundFunc(w, r, nil)
|
notFoundFunc(w, r, nil)
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
|
@ -331,7 +332,7 @@ func (s *httpdServer) verifyCSRFHeader(next http.Handler) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !util.Contains(token.Audience(), tokenAudienceCSRF) {
|
if !slices.Contains(token.Audience(), tokenAudienceCSRF) {
|
||||||
logger.Debug(logSender, "", "error validating CSRF header token audience")
|
logger.Debug(logSender, "", "error validating CSRF header token audience")
|
||||||
sendAPIResponse(w, r, errors.New("the token is not valid"), "", http.StatusForbidden)
|
sendAPIResponse(w, r, errors.New("the token is not valid"), "", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
|
@ -571,11 +572,11 @@ func authenticateUserWithAPIKey(username, keyID string, tokenAuth *jwtauth.JWTAu
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkPartialAuth(w http.ResponseWriter, r *http.Request, audience string, tokenAudience []string) error {
|
func checkPartialAuth(w http.ResponseWriter, r *http.Request, audience string, tokenAudience []string) error {
|
||||||
if audience == tokenAudienceWebAdmin && util.Contains(tokenAudience, tokenAudienceWebAdminPartial) {
|
if audience == tokenAudienceWebAdmin && slices.Contains(tokenAudience, tokenAudienceWebAdminPartial) {
|
||||||
http.Redirect(w, r, webAdminTwoFactorPath, http.StatusFound)
|
http.Redirect(w, r, webAdminTwoFactorPath, http.StatusFound)
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
if audience == tokenAudienceWebClient && util.Contains(tokenAudience, tokenAudienceWebClientPartial) {
|
if audience == tokenAudienceWebClient && slices.Contains(tokenAudience, tokenAudienceWebClientPartial) {
|
||||||
http.Redirect(w, r, webClientTwoFactorPath, http.StatusFound)
|
http.Redirect(w, r, webClientTwoFactorPath, http.StatusFound)
|
||||||
return errInvalidToken
|
return errInvalidToken
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -143,7 +144,7 @@ func (o *OIDC) initialize() error {
|
||||||
if o.RedirectBaseURL == "" {
|
if o.RedirectBaseURL == "" {
|
||||||
return errors.New("oidc: redirect base URL cannot be empty")
|
return errors.New("oidc: redirect base URL cannot be empty")
|
||||||
}
|
}
|
||||||
if !util.Contains(o.Scopes, oidc.ScopeOpenID) {
|
if !slices.Contains(o.Scopes, oidc.ScopeOpenID) {
|
||||||
return fmt.Errorf("oidc: required scope %q is not set", oidc.ScopeOpenID)
|
return fmt.Errorf("oidc: required scope %q is not set", oidc.ScopeOpenID)
|
||||||
}
|
}
|
||||||
if o.ClientSecretFile != "" {
|
if o.ClientSecretFile != "" {
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -355,7 +356,7 @@ func (s *httpdServer) handleWebClientTwoFactorRecoveryPost(w http.ResponseWriter
|
||||||
util.NewI18nError(dataprovider.ErrInvalidCredentials, util.I18nErrorInvalidCredentials))
|
util.NewI18nError(dataprovider.ErrInvalidCredentials, util.I18nErrorInvalidCredentials))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !userMerged.Filters.TOTPConfig.Enabled || !util.Contains(userMerged.Filters.TOTPConfig.Protocols, common.ProtocolHTTP) {
|
if !userMerged.Filters.TOTPConfig.Enabled || !slices.Contains(userMerged.Filters.TOTPConfig.Protocols, common.ProtocolHTTP) {
|
||||||
s.renderClientTwoFactorPage(w, r, util.NewI18nError(
|
s.renderClientTwoFactorPage(w, r, util.NewI18nError(
|
||||||
util.NewValidationError("two factory authentication is not enabled"), util.I18n2FADisabled))
|
util.NewValidationError("two factory authentication is not enabled"), util.I18n2FADisabled))
|
||||||
return
|
return
|
||||||
|
@ -423,7 +424,7 @@ func (s *httpdServer) handleWebClientTwoFactorPost(w http.ResponseWriter, r *htt
|
||||||
s.renderClientTwoFactorPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCredentials))
|
s.renderClientTwoFactorPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCredentials))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !user.Filters.TOTPConfig.Enabled || !util.Contains(user.Filters.TOTPConfig.Protocols, common.ProtocolHTTP) {
|
if !user.Filters.TOTPConfig.Enabled || !slices.Contains(user.Filters.TOTPConfig.Protocols, common.ProtocolHTTP) {
|
||||||
updateLoginMetrics(&user, dataprovider.LoginMethodPassword, ipAddr, common.ErrInternalFailure)
|
updateLoginMetrics(&user, dataprovider.LoginMethodPassword, ipAddr, common.ErrInternalFailure)
|
||||||
s.renderClientTwoFactorPage(w, r, util.NewI18nError(common.ErrInternalFailure, util.I18n2FADisabled))
|
s.renderClientTwoFactorPage(w, r, util.NewI18nError(common.ErrInternalFailure, util.I18n2FADisabled))
|
||||||
return
|
return
|
||||||
|
@ -743,7 +744,7 @@ func (s *httpdServer) loginUser(
|
||||||
}
|
}
|
||||||
|
|
||||||
audience := tokenAudienceWebClient
|
audience := tokenAudienceWebClient
|
||||||
if user.Filters.TOTPConfig.Enabled && util.Contains(user.Filters.TOTPConfig.Protocols, common.ProtocolHTTP) &&
|
if user.Filters.TOTPConfig.Enabled && slices.Contains(user.Filters.TOTPConfig.Protocols, common.ProtocolHTTP) &&
|
||||||
user.CanManageMFA() && !isSecondFactorAuth {
|
user.CanManageMFA() && !isSecondFactorAuth {
|
||||||
audience = tokenAudienceWebClientPartial
|
audience = tokenAudienceWebClientPartial
|
||||||
}
|
}
|
||||||
|
@ -863,7 +864,7 @@ func (s *httpdServer) getUserToken(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Filters.TOTPConfig.Enabled && util.Contains(user.Filters.TOTPConfig.Protocols, common.ProtocolHTTP) {
|
if user.Filters.TOTPConfig.Enabled && slices.Contains(user.Filters.TOTPConfig.Protocols, common.ProtocolHTTP) {
|
||||||
passcode := r.Header.Get(otpHeaderCode)
|
passcode := r.Header.Get(otpHeaderCode)
|
||||||
if passcode == "" {
|
if passcode == "" {
|
||||||
logger.Debug(logSender, "", "TOTP enabled for user %q and not passcode provided, authentication refused", user.Username)
|
logger.Debug(logSender, "", "TOTP enabled for user %q and not passcode provided, authentication refused", user.Username)
|
||||||
|
@ -1009,7 +1010,7 @@ func (s *httpdServer) checkCookieExpiration(w http.ResponseWriter, r *http.Reque
|
||||||
if time.Until(token.Expiration()) > tokenRefreshThreshold {
|
if time.Until(token.Expiration()) > tokenRefreshThreshold {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if util.Contains(token.Audience(), tokenAudienceWebClient) {
|
if slices.Contains(token.Audience(), tokenAudienceWebClient) {
|
||||||
s.refreshClientToken(w, r, &tokenClaims)
|
s.refreshClientToken(w, r, &tokenClaims)
|
||||||
} else {
|
} else {
|
||||||
s.refreshAdminToken(w, r, &tokenClaims)
|
s.refreshAdminToken(w, r, &tokenClaims)
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -1488,13 +1489,13 @@ func getFiltersFromUserPostFields(r *http.Request) (sdk.BaseUserFilters, error)
|
||||||
filters.PasswordStrength = passwordStrength
|
filters.PasswordStrength = passwordStrength
|
||||||
filters.AccessTime = getAccessTimeRestrictionsFromPostFields(r)
|
filters.AccessTime = getAccessTimeRestrictionsFromPostFields(r)
|
||||||
hooks := r.Form["hooks"]
|
hooks := r.Form["hooks"]
|
||||||
if util.Contains(hooks, "external_auth_disabled") {
|
if slices.Contains(hooks, "external_auth_disabled") {
|
||||||
filters.Hooks.ExternalAuthDisabled = true
|
filters.Hooks.ExternalAuthDisabled = true
|
||||||
}
|
}
|
||||||
if util.Contains(hooks, "pre_login_disabled") {
|
if slices.Contains(hooks, "pre_login_disabled") {
|
||||||
filters.Hooks.PreLoginDisabled = true
|
filters.Hooks.PreLoginDisabled = true
|
||||||
}
|
}
|
||||||
if util.Contains(hooks, "check_password_disabled") {
|
if slices.Contains(hooks, "check_password_disabled") {
|
||||||
filters.Hooks.CheckPasswordDisabled = true
|
filters.Hooks.CheckPasswordDisabled = true
|
||||||
}
|
}
|
||||||
filters.IsAnonymous = r.Form.Get("is_anonymous") != ""
|
filters.IsAnonymous = r.Form.Get("is_anonymous") != ""
|
||||||
|
@ -2215,7 +2216,7 @@ func getFoldersRetentionFromPostFields(r *http.Request) ([]dataprovider.FolderRe
|
||||||
res = append(res, dataprovider.FolderRetention{
|
res = append(res, dataprovider.FolderRetention{
|
||||||
Path: p,
|
Path: p,
|
||||||
Retention: retention,
|
Retention: retention,
|
||||||
DeleteEmptyDirs: util.Contains(opts, "1"),
|
DeleteEmptyDirs: slices.Contains(opts, "1"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2557,9 +2558,9 @@ func getEventRuleActionsFromPostFields(r *http.Request) []dataprovider.EventActi
|
||||||
},
|
},
|
||||||
Order: order + 1,
|
Order: order + 1,
|
||||||
Options: dataprovider.EventActionOptions{
|
Options: dataprovider.EventActionOptions{
|
||||||
IsFailureAction: util.Contains(options, "1"),
|
IsFailureAction: slices.Contains(options, "1"),
|
||||||
StopOnFailure: util.Contains(options, "2"),
|
StopOnFailure: slices.Contains(options, "2"),
|
||||||
ExecuteSync: util.Contains(options, "3"),
|
ExecuteSync: slices.Contains(options, "3"),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -1463,7 +1464,7 @@ func (s *httpdServer) handleClientAddSharePost(w http.ResponseWriter, r *http.Re
|
||||||
share.LastUseAt = 0
|
share.LastUseAt = 0
|
||||||
share.Username = claims.Username
|
share.Username = claims.Username
|
||||||
if share.Password == "" {
|
if share.Password == "" {
|
||||||
if util.Contains(claims.Permissions, sdk.WebClientShareNoPasswordDisabled) {
|
if slices.Contains(claims.Permissions, sdk.WebClientShareNoPasswordDisabled) {
|
||||||
s.renderAddUpdateSharePage(w, r, share,
|
s.renderAddUpdateSharePage(w, r, share,
|
||||||
util.NewI18nError(util.NewValidationError("You are not allowed to share files/folders without password"), util.I18nErrorShareNoPwd),
|
util.NewI18nError(util.NewValidationError("You are not allowed to share files/folders without password"), util.I18nErrorShareNoPwd),
|
||||||
true)
|
true)
|
||||||
|
@ -1532,7 +1533,7 @@ func (s *httpdServer) handleClientUpdateSharePost(w http.ResponseWriter, r *http
|
||||||
updatedShare.Password = share.Password
|
updatedShare.Password = share.Password
|
||||||
}
|
}
|
||||||
if updatedShare.Password == "" {
|
if updatedShare.Password == "" {
|
||||||
if util.Contains(claims.Permissions, sdk.WebClientShareNoPasswordDisabled) {
|
if slices.Contains(claims.Permissions, sdk.WebClientShareNoPasswordDisabled) {
|
||||||
s.renderAddUpdateSharePage(w, r, updatedShare,
|
s.renderAddUpdateSharePage(w, r, updatedShare,
|
||||||
util.NewI18nError(util.NewValidationError("You are not allowed to share files/folders without password"), util.I18nErrorShareNoPwd),
|
util.NewI18nError(util.NewValidationError("You are not allowed to share files/folders without password"), util.I18nErrorShareNoPwd),
|
||||||
false)
|
false)
|
||||||
|
@ -2015,7 +2016,7 @@ func doCheckExist(w http.ResponseWriter, r *http.Request, connection *Connection
|
||||||
}
|
}
|
||||||
existing := make([]map[string]any, 0)
|
existing := make([]map[string]any, 0)
|
||||||
for _, info := range contents {
|
for _, info := range contents {
|
||||||
if util.Contains(filesList.Files, info.Name()) {
|
if slices.Contains(filesList.Files, info.Name()) {
|
||||||
res := make(map[string]any)
|
res := make(map[string]any)
|
||||||
res["name"] = info.Name()
|
res["name"] = info.Name()
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -36,7 +37,6 @@ import (
|
||||||
"github.com/drakkan/sftpgo/v2/internal/httpclient"
|
"github.com/drakkan/sftpgo/v2/internal/httpclient"
|
||||||
"github.com/drakkan/sftpgo/v2/internal/httpd"
|
"github.com/drakkan/sftpgo/v2/internal/httpd"
|
||||||
"github.com/drakkan/sftpgo/v2/internal/kms"
|
"github.com/drakkan/sftpgo/v2/internal/kms"
|
||||||
"github.com/drakkan/sftpgo/v2/internal/util"
|
|
||||||
"github.com/drakkan/sftpgo/v2/internal/version"
|
"github.com/drakkan/sftpgo/v2/internal/version"
|
||||||
"github.com/drakkan/sftpgo/v2/internal/vfs"
|
"github.com/drakkan/sftpgo/v2/internal/vfs"
|
||||||
)
|
)
|
||||||
|
@ -1679,7 +1679,7 @@ func checkEventConditionOptions(expected, actual dataprovider.ConditionOptions)
|
||||||
return errors.New("condition protocols mismatch")
|
return errors.New("condition protocols mismatch")
|
||||||
}
|
}
|
||||||
for _, v := range expected.Protocols {
|
for _, v := range expected.Protocols {
|
||||||
if !util.Contains(actual.Protocols, v) {
|
if !slices.Contains(actual.Protocols, v) {
|
||||||
return errors.New("condition protocols content mismatch")
|
return errors.New("condition protocols content mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1687,7 +1687,7 @@ func checkEventConditionOptions(expected, actual dataprovider.ConditionOptions)
|
||||||
return errors.New("condition provider objects mismatch")
|
return errors.New("condition provider objects mismatch")
|
||||||
}
|
}
|
||||||
for _, v := range expected.ProviderObjects {
|
for _, v := range expected.ProviderObjects {
|
||||||
if !util.Contains(actual.ProviderObjects, v) {
|
if !slices.Contains(actual.ProviderObjects, v) {
|
||||||
return errors.New("condition provider objects content mismatch")
|
return errors.New("condition provider objects content mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1705,7 +1705,7 @@ func checkEventConditions(expected, actual dataprovider.EventConditions) error {
|
||||||
return errors.New("fs events mismatch")
|
return errors.New("fs events mismatch")
|
||||||
}
|
}
|
||||||
for _, v := range expected.FsEvents {
|
for _, v := range expected.FsEvents {
|
||||||
if !util.Contains(actual.FsEvents, v) {
|
if !slices.Contains(actual.FsEvents, v) {
|
||||||
return errors.New("fs events content mismatch")
|
return errors.New("fs events content mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1713,7 +1713,7 @@ func checkEventConditions(expected, actual dataprovider.EventConditions) error {
|
||||||
return errors.New("provider events mismatch")
|
return errors.New("provider events mismatch")
|
||||||
}
|
}
|
||||||
for _, v := range expected.ProviderEvents {
|
for _, v := range expected.ProviderEvents {
|
||||||
if !util.Contains(actual.ProviderEvents, v) {
|
if !slices.Contains(actual.ProviderEvents, v) {
|
||||||
return errors.New("provider events content mismatch")
|
return errors.New("provider events content mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1948,7 +1948,7 @@ func checkAdmin(expected, actual *dataprovider.Admin) error {
|
||||||
return errors.New("permissions mismatch")
|
return errors.New("permissions mismatch")
|
||||||
}
|
}
|
||||||
for _, p := range expected.Permissions {
|
for _, p := range expected.Permissions {
|
||||||
if !util.Contains(actual.Permissions, p) {
|
if !slices.Contains(actual.Permissions, p) {
|
||||||
return errors.New("permissions content mismatch")
|
return errors.New("permissions content mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1966,7 +1966,7 @@ func compareAdminFilters(expected, actual dataprovider.AdminFilters) error {
|
||||||
return errors.New("allow list mismatch")
|
return errors.New("allow list mismatch")
|
||||||
}
|
}
|
||||||
for _, v := range expected.AllowList {
|
for _, v := range expected.AllowList {
|
||||||
if !util.Contains(actual.AllowList, v) {
|
if !slices.Contains(actual.AllowList, v) {
|
||||||
return errors.New("allow list content mismatch")
|
return errors.New("allow list content mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2057,7 +2057,7 @@ func compareUserPermissions(expected map[string][]string, actual map[string][]st
|
||||||
for dir, perms := range expected {
|
for dir, perms := range expected {
|
||||||
if actualPerms, ok := actual[dir]; ok {
|
if actualPerms, ok := actual[dir]; ok {
|
||||||
for _, v := range actualPerms {
|
for _, v := range actualPerms {
|
||||||
if !util.Contains(perms, v) {
|
if !slices.Contains(perms, v) {
|
||||||
return errors.New("permissions contents mismatch")
|
return errors.New("permissions contents mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2310,7 +2310,7 @@ func compareSFTPFsConfig(expected *vfs.Filesystem, actual *vfs.Filesystem) error
|
||||||
return errors.New("SFTPFs fingerprints mismatch")
|
return errors.New("SFTPFs fingerprints mismatch")
|
||||||
}
|
}
|
||||||
for _, value := range actual.SFTPConfig.Fingerprints {
|
for _, value := range actual.SFTPConfig.Fingerprints {
|
||||||
if !util.Contains(expected.SFTPConfig.Fingerprints, value) {
|
if !slices.Contains(expected.SFTPConfig.Fingerprints, value) {
|
||||||
return errors.New("SFTPFs fingerprints mismatch")
|
return errors.New("SFTPFs fingerprints mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2401,27 +2401,27 @@ func checkEncryptedSecret(expected, actual *kms.Secret) error {
|
||||||
|
|
||||||
func compareUserFilterSubStructs(expected sdk.BaseUserFilters, actual sdk.BaseUserFilters) error {
|
func compareUserFilterSubStructs(expected sdk.BaseUserFilters, actual sdk.BaseUserFilters) error {
|
||||||
for _, IPMask := range expected.AllowedIP {
|
for _, IPMask := range expected.AllowedIP {
|
||||||
if !util.Contains(actual.AllowedIP, IPMask) {
|
if !slices.Contains(actual.AllowedIP, IPMask) {
|
||||||
return errors.New("allowed IP contents mismatch")
|
return errors.New("allowed IP contents mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, IPMask := range expected.DeniedIP {
|
for _, IPMask := range expected.DeniedIP {
|
||||||
if !util.Contains(actual.DeniedIP, IPMask) {
|
if !slices.Contains(actual.DeniedIP, IPMask) {
|
||||||
return errors.New("denied IP contents mismatch")
|
return errors.New("denied IP contents mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, method := range expected.DeniedLoginMethods {
|
for _, method := range expected.DeniedLoginMethods {
|
||||||
if !util.Contains(actual.DeniedLoginMethods, method) {
|
if !slices.Contains(actual.DeniedLoginMethods, method) {
|
||||||
return errors.New("denied login methods contents mismatch")
|
return errors.New("denied login methods contents mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, protocol := range expected.DeniedProtocols {
|
for _, protocol := range expected.DeniedProtocols {
|
||||||
if !util.Contains(actual.DeniedProtocols, protocol) {
|
if !slices.Contains(actual.DeniedProtocols, protocol) {
|
||||||
return errors.New("denied protocols contents mismatch")
|
return errors.New("denied protocols contents mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, options := range expected.WebClient {
|
for _, options := range expected.WebClient {
|
||||||
if !util.Contains(actual.WebClient, options) {
|
if !slices.Contains(actual.WebClient, options) {
|
||||||
return errors.New("web client options contents mismatch")
|
return errors.New("web client options contents mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2430,7 +2430,7 @@ func compareUserFilterSubStructs(expected sdk.BaseUserFilters, actual sdk.BaseUs
|
||||||
return errors.New("TLS certs mismatch")
|
return errors.New("TLS certs mismatch")
|
||||||
}
|
}
|
||||||
for _, cert := range expected.TLSCerts {
|
for _, cert := range expected.TLSCerts {
|
||||||
if !util.Contains(actual.TLSCerts, cert) {
|
if !slices.Contains(actual.TLSCerts, cert) {
|
||||||
return errors.New("TLS certs content mismatch")
|
return errors.New("TLS certs content mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2527,7 +2527,7 @@ func checkFilterMatch(expected []string, actual []string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, e := range expected {
|
for _, e := range expected {
|
||||||
if !util.Contains(actual, strings.ToLower(e)) {
|
if !slices.Contains(actual, strings.ToLower(e)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2570,7 +2570,7 @@ func compareUserBandwidthLimitFilters(expected sdk.BaseUserFilters, actual sdk.B
|
||||||
return errors.New("bandwidth filters sources mismatch")
|
return errors.New("bandwidth filters sources mismatch")
|
||||||
}
|
}
|
||||||
for _, source := range actual.BandwidthLimits[idx].Sources {
|
for _, source := range actual.BandwidthLimits[idx].Sources {
|
||||||
if !util.Contains(l.Sources, source) {
|
if !slices.Contains(l.Sources, source) {
|
||||||
return errors.New("bandwidth filters source mismatch")
|
return errors.New("bandwidth filters source mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2680,7 +2680,7 @@ func compareEventActionEmailConfigFields(expected, actual dataprovider.EventActi
|
||||||
return errors.New("email recipients mismatch")
|
return errors.New("email recipients mismatch")
|
||||||
}
|
}
|
||||||
for _, v := range expected.Recipients {
|
for _, v := range expected.Recipients {
|
||||||
if !util.Contains(actual.Recipients, v) {
|
if !slices.Contains(actual.Recipients, v) {
|
||||||
return errors.New("email recipients content mismatch")
|
return errors.New("email recipients content mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2688,7 +2688,7 @@ func compareEventActionEmailConfigFields(expected, actual dataprovider.EventActi
|
||||||
return errors.New("email bcc mismatch")
|
return errors.New("email bcc mismatch")
|
||||||
}
|
}
|
||||||
for _, v := range expected.Bcc {
|
for _, v := range expected.Bcc {
|
||||||
if !util.Contains(actual.Bcc, v) {
|
if !slices.Contains(actual.Bcc, v) {
|
||||||
return errors.New("email bcc content mismatch")
|
return errors.New("email bcc content mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2705,7 +2705,7 @@ func compareEventActionEmailConfigFields(expected, actual dataprovider.EventActi
|
||||||
return errors.New("email attachments mismatch")
|
return errors.New("email attachments mismatch")
|
||||||
}
|
}
|
||||||
for _, v := range expected.Attachments {
|
for _, v := range expected.Attachments {
|
||||||
if !util.Contains(actual.Attachments, v) {
|
if !slices.Contains(actual.Attachments, v) {
|
||||||
return errors.New("email attachments content mismatch")
|
return errors.New("email attachments content mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2720,7 +2720,7 @@ func compareEventActionFsCompressFields(expected, actual dataprovider.EventActio
|
||||||
return errors.New("fs compress paths mismatch")
|
return errors.New("fs compress paths mismatch")
|
||||||
}
|
}
|
||||||
for _, v := range expected.Paths {
|
for _, v := range expected.Paths {
|
||||||
if !util.Contains(actual.Paths, v) {
|
if !slices.Contains(actual.Paths, v) {
|
||||||
return errors.New("fs compress paths content mismatch")
|
return errors.New("fs compress paths content mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2741,7 +2741,7 @@ func compareEventActionFsConfigFields(expected, actual dataprovider.EventActionF
|
||||||
return errors.New("fs deletes mismatch")
|
return errors.New("fs deletes mismatch")
|
||||||
}
|
}
|
||||||
for _, v := range expected.Deletes {
|
for _, v := range expected.Deletes {
|
||||||
if !util.Contains(actual.Deletes, v) {
|
if !slices.Contains(actual.Deletes, v) {
|
||||||
return errors.New("fs deletes content mismatch")
|
return errors.New("fs deletes content mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2749,7 +2749,7 @@ func compareEventActionFsConfigFields(expected, actual dataprovider.EventActionF
|
||||||
return errors.New("fs mkdirs mismatch")
|
return errors.New("fs mkdirs mismatch")
|
||||||
}
|
}
|
||||||
for _, v := range expected.MkDirs {
|
for _, v := range expected.MkDirs {
|
||||||
if !util.Contains(actual.MkDirs, v) {
|
if !slices.Contains(actual.MkDirs, v) {
|
||||||
return errors.New("fs mkdir content mismatch")
|
return errors.New("fs mkdir content mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2757,7 +2757,7 @@ func compareEventActionFsConfigFields(expected, actual dataprovider.EventActionF
|
||||||
return errors.New("fs exist mismatch")
|
return errors.New("fs exist mismatch")
|
||||||
}
|
}
|
||||||
for _, v := range expected.Exist {
|
for _, v := range expected.Exist {
|
||||||
if !util.Contains(actual.Exist, v) {
|
if !slices.Contains(actual.Exist, v) {
|
||||||
return errors.New("fs exist content mismatch")
|
return errors.New("fs exist content mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2788,7 +2788,7 @@ func compareEventActionCmdConfigFields(expected, actual dataprovider.EventAction
|
||||||
return errors.New("cmd args mismatch")
|
return errors.New("cmd args mismatch")
|
||||||
}
|
}
|
||||||
for _, v := range expected.Args {
|
for _, v := range expected.Args {
|
||||||
if !util.Contains(actual.Args, v) {
|
if !slices.Contains(actual.Args, v) {
|
||||||
return errors.New("cmd args content mismatch")
|
return errors.New("cmd args content mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ package plugin
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
"github.com/hashicorp/go-plugin"
|
"github.com/hashicorp/go-plugin"
|
||||||
|
@ -25,7 +26,6 @@ import (
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/v2/internal/kms"
|
"github.com/drakkan/sftpgo/v2/internal/kms"
|
||||||
"github.com/drakkan/sftpgo/v2/internal/logger"
|
"github.com/drakkan/sftpgo/v2/internal/logger"
|
||||||
"github.com/drakkan/sftpgo/v2/internal/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -41,10 +41,10 @@ type KMSConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *KMSConfig) validate() error {
|
func (c *KMSConfig) validate() error {
|
||||||
if !util.Contains(validKMSSchemes, c.Scheme) {
|
if !slices.Contains(validKMSSchemes, c.Scheme) {
|
||||||
return fmt.Errorf("invalid kms scheme: %v", c.Scheme)
|
return fmt.Errorf("invalid kms scheme: %v", c.Scheme)
|
||||||
}
|
}
|
||||||
if !util.Contains(validKMSEncryptedStatuses, c.EncryptedStatus) {
|
if !slices.Contains(validKMSEncryptedStatuses, c.EncryptedStatus) {
|
||||||
return fmt.Errorf("invalid kms encrypted status: %v", c.EncryptedStatus)
|
return fmt.Errorf("invalid kms encrypted status: %v", c.EncryptedStatus)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -16,6 +16,7 @@ package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -24,7 +25,6 @@ import (
|
||||||
"github.com/sftpgo/sdk/plugin/notifier"
|
"github.com/sftpgo/sdk/plugin/notifier"
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/v2/internal/logger"
|
"github.com/drakkan/sftpgo/v2/internal/logger"
|
||||||
"github.com/drakkan/sftpgo/v2/internal/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NotifierConfig defines configuration parameters for notifiers plugins
|
// NotifierConfig defines configuration parameters for notifiers plugins
|
||||||
|
@ -220,7 +220,7 @@ func (p *notifierPlugin) canQueueEvent(timestamp int64) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *notifierPlugin) notifyFsAction(event *notifier.FsEvent) {
|
func (p *notifierPlugin) notifyFsAction(event *notifier.FsEvent) {
|
||||||
if !util.Contains(p.config.NotifierOptions.FsEvents, event.Action) {
|
if !slices.Contains(p.config.NotifierOptions.FsEvents, event.Action) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,8 +233,8 @@ func (p *notifierPlugin) notifyFsAction(event *notifier.FsEvent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *notifierPlugin) notifyProviderAction(event *notifier.ProviderEvent, object Renderer) {
|
func (p *notifierPlugin) notifyProviderAction(event *notifier.ProviderEvent, object Renderer) {
|
||||||
if !util.Contains(p.config.NotifierOptions.ProviderEvents, event.Action) ||
|
if !slices.Contains(p.config.NotifierOptions.ProviderEvents, event.Action) ||
|
||||||
!util.Contains(p.config.NotifierOptions.ProviderObjects, event.ObjectType) {
|
!slices.Contains(p.config.NotifierOptions.ProviderObjects, event.ObjectType) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
@ -336,7 +337,7 @@ func (m *Manager) NotifyLogEvent(event notifier.LogEventType, protocol, username
|
||||||
var e *notifier.LogEvent
|
var e *notifier.LogEvent
|
||||||
|
|
||||||
for _, n := range m.notifiers {
|
for _, n := range m.notifiers {
|
||||||
if util.Contains(n.config.NotifierOptions.LogEvents, int(event)) {
|
if slices.Contains(n.config.NotifierOptions.LogEvents, int(event)) {
|
||||||
if e == nil {
|
if e == nil {
|
||||||
message := ""
|
message := ""
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -20,6 +20,7 @@ package service
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sftpgo/sdk"
|
"github.com/sftpgo/sdk"
|
||||||
|
@ -211,7 +212,7 @@ func configurePortableSFTPService(port int, enabledSSHCommands []string) {
|
||||||
} else {
|
} else {
|
||||||
sftpdConf.Bindings[0].Port = 0
|
sftpdConf.Bindings[0].Port = 0
|
||||||
}
|
}
|
||||||
if util.Contains(enabledSSHCommands, "*") {
|
if slices.Contains(enabledSSHCommands, "*") {
|
||||||
sftpdConf.EnabledSSHCommands = sftpd.GetSupportedSSHCommands()
|
sftpdConf.EnabledSSHCommands = sftpd.GetSupportedSSHCommands()
|
||||||
} else {
|
} else {
|
||||||
sftpdConf.EnabledSSHCommands = enabledSSHCommands
|
sftpdConf.EnabledSSHCommands = enabledSSHCommands
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -418,7 +419,7 @@ func TestSupportedSSHCommands(t *testing.T) {
|
||||||
assert.Equal(t, len(supportedSSHCommands), len(cmds))
|
assert.Equal(t, len(supportedSSHCommands), len(cmds))
|
||||||
|
|
||||||
for _, c := range cmds {
|
for _, c := range cmds {
|
||||||
assert.True(t, util.Contains(supportedSSHCommands, c))
|
assert.True(t, slices.Contains(supportedSSHCommands, c))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -842,7 +843,7 @@ func TestRsyncOptions(t *testing.T) {
|
||||||
}
|
}
|
||||||
cmd, err := sshCmd.getSystemCommand()
|
cmd, err := sshCmd.getSystemCommand()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, util.Contains(cmd.cmd.Args, "--safe-links"),
|
assert.True(t, slices.Contains(cmd.cmd.Args, "--safe-links"),
|
||||||
"--safe-links must be added if the user has the create symlinks permission")
|
"--safe-links must be added if the user has the create symlinks permission")
|
||||||
|
|
||||||
permissions["/"] = []string{dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermCreateDirs,
|
permissions["/"] = []string{dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermCreateDirs,
|
||||||
|
@ -859,7 +860,7 @@ func TestRsyncOptions(t *testing.T) {
|
||||||
}
|
}
|
||||||
cmd, err = sshCmd.getSystemCommand()
|
cmd, err = sshCmd.getSystemCommand()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, util.Contains(cmd.cmd.Args, "--munge-links"),
|
assert.True(t, slices.Contains(cmd.cmd.Args, "--munge-links"),
|
||||||
"--munge-links must be added if the user has the create symlinks permission")
|
"--munge-links must be added if the user has the create symlinks permission")
|
||||||
|
|
||||||
sshCmd.connection.User.VirtualFolders = append(sshCmd.connection.User.VirtualFolders, vfs.VirtualFolder{
|
sshCmd.connection.User.VirtualFolders = append(sshCmd.connection.User.VirtualFolders, vfs.VirtualFolder{
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -263,13 +264,13 @@ func (c *Configuration) getServerConfig() *ssh.ServerConfig {
|
||||||
func (c *Configuration) updateSupportedAuthentications() {
|
func (c *Configuration) updateSupportedAuthentications() {
|
||||||
serviceStatus.Authentications = util.RemoveDuplicates(serviceStatus.Authentications, false)
|
serviceStatus.Authentications = util.RemoveDuplicates(serviceStatus.Authentications, false)
|
||||||
|
|
||||||
if util.Contains(serviceStatus.Authentications, dataprovider.LoginMethodPassword) &&
|
if slices.Contains(serviceStatus.Authentications, dataprovider.LoginMethodPassword) &&
|
||||||
util.Contains(serviceStatus.Authentications, dataprovider.SSHLoginMethodPublicKey) {
|
slices.Contains(serviceStatus.Authentications, dataprovider.SSHLoginMethodPublicKey) {
|
||||||
serviceStatus.Authentications = append(serviceStatus.Authentications, dataprovider.SSHLoginMethodKeyAndPassword)
|
serviceStatus.Authentications = append(serviceStatus.Authentications, dataprovider.SSHLoginMethodKeyAndPassword)
|
||||||
}
|
}
|
||||||
|
|
||||||
if util.Contains(serviceStatus.Authentications, dataprovider.SSHLoginMethodKeyboardInteractive) &&
|
if slices.Contains(serviceStatus.Authentications, dataprovider.SSHLoginMethodKeyboardInteractive) &&
|
||||||
util.Contains(serviceStatus.Authentications, dataprovider.SSHLoginMethodPublicKey) {
|
slices.Contains(serviceStatus.Authentications, dataprovider.SSHLoginMethodPublicKey) {
|
||||||
serviceStatus.Authentications = append(serviceStatus.Authentications, dataprovider.SSHLoginMethodKeyAndKeyboardInt)
|
serviceStatus.Authentications = append(serviceStatus.Authentications, dataprovider.SSHLoginMethodKeyAndKeyboardInt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -422,7 +423,7 @@ func (c *Configuration) configureKeyAlgos(serverConfig *ssh.ServerConfig) error
|
||||||
c.HostKeyAlgorithms = util.RemoveDuplicates(c.HostKeyAlgorithms, true)
|
c.HostKeyAlgorithms = util.RemoveDuplicates(c.HostKeyAlgorithms, true)
|
||||||
}
|
}
|
||||||
for _, hostKeyAlgo := range c.HostKeyAlgorithms {
|
for _, hostKeyAlgo := range c.HostKeyAlgorithms {
|
||||||
if !util.Contains(supportedHostKeyAlgos, hostKeyAlgo) {
|
if !slices.Contains(supportedHostKeyAlgos, hostKeyAlgo) {
|
||||||
return fmt.Errorf("unsupported host key algorithm %q", hostKeyAlgo)
|
return fmt.Errorf("unsupported host key algorithm %q", hostKeyAlgo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -430,7 +431,7 @@ func (c *Configuration) configureKeyAlgos(serverConfig *ssh.ServerConfig) error
|
||||||
if len(c.PublicKeyAlgorithms) > 0 {
|
if len(c.PublicKeyAlgorithms) > 0 {
|
||||||
c.PublicKeyAlgorithms = util.RemoveDuplicates(c.PublicKeyAlgorithms, true)
|
c.PublicKeyAlgorithms = util.RemoveDuplicates(c.PublicKeyAlgorithms, true)
|
||||||
for _, algo := range c.PublicKeyAlgorithms {
|
for _, algo := range c.PublicKeyAlgorithms {
|
||||||
if !util.Contains(supportedPublicKeyAlgos, algo) {
|
if !slices.Contains(supportedPublicKeyAlgos, algo) {
|
||||||
return fmt.Errorf("unsupported public key authentication algorithm %q", algo)
|
return fmt.Errorf("unsupported public key authentication algorithm %q", algo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -472,7 +473,7 @@ func (c *Configuration) configureSecurityOptions(serverConfig *ssh.ServerConfig)
|
||||||
if kex == keyExchangeCurve25519SHA256LibSSH {
|
if kex == keyExchangeCurve25519SHA256LibSSH {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !util.Contains(supportedKexAlgos, kex) {
|
if !slices.Contains(supportedKexAlgos, kex) {
|
||||||
return fmt.Errorf("unsupported key-exchange algorithm %q", kex)
|
return fmt.Errorf("unsupported key-exchange algorithm %q", kex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -486,7 +487,7 @@ func (c *Configuration) configureSecurityOptions(serverConfig *ssh.ServerConfig)
|
||||||
if len(c.Ciphers) > 0 {
|
if len(c.Ciphers) > 0 {
|
||||||
c.Ciphers = util.RemoveDuplicates(c.Ciphers, true)
|
c.Ciphers = util.RemoveDuplicates(c.Ciphers, true)
|
||||||
for _, cipher := range c.Ciphers {
|
for _, cipher := range c.Ciphers {
|
||||||
if !util.Contains(supportedCiphers, cipher) {
|
if !slices.Contains(supportedCiphers, cipher) {
|
||||||
return fmt.Errorf("unsupported cipher %q", cipher)
|
return fmt.Errorf("unsupported cipher %q", cipher)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -499,7 +500,7 @@ func (c *Configuration) configureSecurityOptions(serverConfig *ssh.ServerConfig)
|
||||||
if len(c.MACs) > 0 {
|
if len(c.MACs) > 0 {
|
||||||
c.MACs = util.RemoveDuplicates(c.MACs, true)
|
c.MACs = util.RemoveDuplicates(c.MACs, true)
|
||||||
for _, mac := range c.MACs {
|
for _, mac := range c.MACs {
|
||||||
if !util.Contains(supportedMACs, mac) {
|
if !slices.Contains(supportedMACs, mac) {
|
||||||
return fmt.Errorf("unsupported MAC algorithm %q", mac)
|
return fmt.Errorf("unsupported MAC algorithm %q", mac)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -785,7 +786,7 @@ func loginUser(user *dataprovider.User, loginMethod, publicKey string, conn ssh.
|
||||||
user.Username, user.HomeDir)
|
user.Username, user.HomeDir)
|
||||||
return nil, fmt.Errorf("cannot login user with invalid home dir: %q", user.HomeDir)
|
return nil, fmt.Errorf("cannot login user with invalid home dir: %q", user.HomeDir)
|
||||||
}
|
}
|
||||||
if util.Contains(user.Filters.DeniedProtocols, common.ProtocolSSH) {
|
if slices.Contains(user.Filters.DeniedProtocols, common.ProtocolSSH) {
|
||||||
logger.Info(logSender, connectionID, "cannot login user %q, protocol SSH is not allowed", user.Username)
|
logger.Info(logSender, connectionID, "cannot login user %q, protocol SSH is not allowed", user.Username)
|
||||||
return nil, fmt.Errorf("protocol SSH is not allowed for user %q", user.Username)
|
return nil, fmt.Errorf("protocol SSH is not allowed for user %q", user.Username)
|
||||||
}
|
}
|
||||||
|
@ -830,14 +831,14 @@ func loginUser(user *dataprovider.User, loginMethod, publicKey string, conn ssh.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Configuration) checkSSHCommands() {
|
func (c *Configuration) checkSSHCommands() {
|
||||||
if util.Contains(c.EnabledSSHCommands, "*") {
|
if slices.Contains(c.EnabledSSHCommands, "*") {
|
||||||
c.EnabledSSHCommands = GetSupportedSSHCommands()
|
c.EnabledSSHCommands = GetSupportedSSHCommands()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sshCommands := []string{}
|
sshCommands := []string{}
|
||||||
for _, command := range c.EnabledSSHCommands {
|
for _, command := range c.EnabledSSHCommands {
|
||||||
command = strings.TrimSpace(command)
|
command = strings.TrimSpace(command)
|
||||||
if util.Contains(supportedSSHCommands, command) {
|
if slices.Contains(supportedSSHCommands, command) {
|
||||||
sshCommands = append(sshCommands, command)
|
sshCommands = append(sshCommands, command)
|
||||||
} else {
|
} else {
|
||||||
logger.Warn(logSender, "", "unsupported ssh command: %q ignored", command)
|
logger.Warn(logSender, "", "unsupported ssh command: %q ignored", command)
|
||||||
|
@ -927,7 +928,7 @@ func (c *Configuration) checkHostKeyAutoGeneration(configDir string) error {
|
||||||
func (c *Configuration) getHostKeyAlgorithms(keyFormat string) []string {
|
func (c *Configuration) getHostKeyAlgorithms(keyFormat string) []string {
|
||||||
var algos []string
|
var algos []string
|
||||||
for _, algo := range algorithmsForKeyFormat(keyFormat) {
|
for _, algo := range algorithmsForKeyFormat(keyFormat) {
|
||||||
if util.Contains(c.HostKeyAlgorithms, algo) {
|
if slices.Contains(c.HostKeyAlgorithms, algo) {
|
||||||
algos = append(algos, algo)
|
algos = append(algos, algo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -986,7 +987,7 @@ func (c *Configuration) checkAndLoadHostKeys(configDir string, serverConfig *ssh
|
||||||
var algos []string
|
var algos []string
|
||||||
for _, algo := range algorithmsForKeyFormat(signer.PublicKey().Type()) {
|
for _, algo := range algorithmsForKeyFormat(signer.PublicKey().Type()) {
|
||||||
if underlyingAlgo, ok := certKeyAlgoNames[algo]; ok {
|
if underlyingAlgo, ok := certKeyAlgoNames[algo]; ok {
|
||||||
if util.Contains(mas.Algorithms(), underlyingAlgo) {
|
if slices.Contains(mas.Algorithms(), underlyingAlgo) {
|
||||||
algos = append(algos, algo)
|
algos = append(algos, algo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1098,12 +1099,12 @@ func (c *Configuration) initializeCertChecker(configDir string) error {
|
||||||
|
|
||||||
func (c *Configuration) getPartialSuccessError(nextAuthMethods []string) error {
|
func (c *Configuration) getPartialSuccessError(nextAuthMethods []string) error {
|
||||||
err := &ssh.PartialSuccessError{}
|
err := &ssh.PartialSuccessError{}
|
||||||
if c.PasswordAuthentication && util.Contains(nextAuthMethods, dataprovider.LoginMethodPassword) {
|
if c.PasswordAuthentication && slices.Contains(nextAuthMethods, dataprovider.LoginMethodPassword) {
|
||||||
err.Next.PasswordCallback = func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
|
err.Next.PasswordCallback = func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
|
||||||
return c.validatePasswordCredentials(conn, password, dataprovider.SSHLoginMethodKeyAndPassword)
|
return c.validatePasswordCredentials(conn, password, dataprovider.SSHLoginMethodKeyAndPassword)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.KeyboardInteractiveAuthentication && util.Contains(nextAuthMethods, dataprovider.SSHLoginMethodKeyboardInteractive) {
|
if c.KeyboardInteractiveAuthentication && slices.Contains(nextAuthMethods, dataprovider.SSHLoginMethodKeyboardInteractive) {
|
||||||
err.Next.KeyboardInteractiveCallback = func(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge) (*ssh.Permissions, error) {
|
err.Next.KeyboardInteractiveCallback = func(conn ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge) (*ssh.Permissions, error) {
|
||||||
return c.validateKeyboardInteractiveCredentials(conn, client, dataprovider.SSHLoginMethodKeyAndKeyboardInt, true)
|
return c.validateKeyboardInteractiveCredentials(conn, client, dataprovider.SSHLoginMethodKeyAndKeyboardInt, true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -8639,8 +8640,8 @@ func TestUserAllowedLoginMethods(t *testing.T) {
|
||||||
allowedMethods = user.GetAllowedLoginMethods()
|
allowedMethods = user.GetAllowedLoginMethods()
|
||||||
assert.Equal(t, 4, len(allowedMethods))
|
assert.Equal(t, 4, len(allowedMethods))
|
||||||
|
|
||||||
assert.True(t, util.Contains(allowedMethods, dataprovider.SSHLoginMethodKeyAndKeyboardInt))
|
assert.True(t, slices.Contains(allowedMethods, dataprovider.SSHLoginMethodKeyAndKeyboardInt))
|
||||||
assert.True(t, util.Contains(allowedMethods, dataprovider.SSHLoginMethodKeyAndPassword))
|
assert.True(t, slices.Contains(allowedMethods, dataprovider.SSHLoginMethodKeyAndPassword))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUserPartialAuth(t *testing.T) {
|
func TestUserPartialAuth(t *testing.T) {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -91,7 +92,7 @@ func processSSHCommand(payload []byte, connection *Connection, enabledSSHCommand
|
||||||
name, args, err := parseCommandPayload(msg.Command)
|
name, args, err := parseCommandPayload(msg.Command)
|
||||||
connection.Log(logger.LevelDebug, "new ssh command: %q args: %v num args: %d user: %s, error: %v",
|
connection.Log(logger.LevelDebug, "new ssh command: %q args: %v num args: %d user: %s, error: %v",
|
||||||
name, args, len(args), connection.User.Username, err)
|
name, args, len(args), connection.User.Username, err)
|
||||||
if err == nil && util.Contains(enabledSSHCommands, name) {
|
if err == nil && slices.Contains(enabledSSHCommands, name) {
|
||||||
connection.command = msg.Command
|
connection.command = msg.Command
|
||||||
if name == scpCmdName && len(args) >= 2 {
|
if name == scpCmdName && len(args) >= 2 {
|
||||||
connection.SetProtocol(common.ProtocolSCP)
|
connection.SetProtocol(common.ProtocolSCP)
|
||||||
|
@ -139,9 +140,9 @@ func (c *sshCommand) handle() (err error) {
|
||||||
defer common.Connections.Remove(c.connection.GetID())
|
defer common.Connections.Remove(c.connection.GetID())
|
||||||
|
|
||||||
c.connection.UpdateLastActivity()
|
c.connection.UpdateLastActivity()
|
||||||
if util.Contains(sshHashCommands, c.command) {
|
if slices.Contains(sshHashCommands, c.command) {
|
||||||
return c.handleHashCommands()
|
return c.handleHashCommands()
|
||||||
} else if util.Contains(systemCommands, c.command) {
|
} else if slices.Contains(systemCommands, c.command) {
|
||||||
command, err := c.getSystemCommand()
|
command, err := c.getSystemCommand()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.sendErrorResponse(err)
|
return c.sendErrorResponse(err)
|
||||||
|
@ -429,11 +430,11 @@ func (c *sshCommand) getSystemCommand() (systemCommand, error) {
|
||||||
// If the user cannot create symlinks we add the option --munge-links, if it is not
|
// If the user cannot create symlinks we add the option --munge-links, if it is not
|
||||||
// already set. This should make symlinks unusable (but manually recoverable)
|
// already set. This should make symlinks unusable (but manually recoverable)
|
||||||
if c.connection.User.HasPerm(dataprovider.PermCreateSymlinks, c.getDestPath()) {
|
if c.connection.User.HasPerm(dataprovider.PermCreateSymlinks, c.getDestPath()) {
|
||||||
if !util.Contains(args, "--safe-links") {
|
if !slices.Contains(args, "--safe-links") {
|
||||||
args = append([]string{"--safe-links"}, args...)
|
args = append([]string{"--safe-links"}, args...)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !util.Contains(args, "--munge-links") {
|
if !slices.Contains(args, "--munge-links") {
|
||||||
args = append([]string{"--munge-links"}, args...)
|
args = append([]string{"--munge-links"}, args...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -27,7 +28,6 @@ import (
|
||||||
"golang.org/x/oauth2/microsoft"
|
"golang.org/x/oauth2/microsoft"
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/v2/internal/logger"
|
"github.com/drakkan/sftpgo/v2/internal/logger"
|
||||||
"github.com/drakkan/sftpgo/v2/internal/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Supported OAuth2 providers
|
// Supported OAuth2 providers
|
||||||
|
@ -56,7 +56,7 @@ type OAuth2Config struct {
|
||||||
|
|
||||||
// Validate validates and initializes the configuration
|
// Validate validates and initializes the configuration
|
||||||
func (c *OAuth2Config) Validate() error {
|
func (c *OAuth2Config) Validate() error {
|
||||||
if !util.Contains(supportedOAuth2Providers, c.Provider) {
|
if !slices.Contains(supportedOAuth2Providers, c.Provider) {
|
||||||
return fmt.Errorf("smtp oauth2: unsupported provider %d", c.Provider)
|
return fmt.Errorf("smtp oauth2: unsupported provider %d", c.Provider)
|
||||||
}
|
}
|
||||||
if c.ClientID == "" {
|
if c.ClientID == "" {
|
||||||
|
|
|
@ -128,16 +128,6 @@ var bytesSizeTable = map[string]uint64{
|
||||||
"e": eByte,
|
"e": eByte,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contains reports whether v is present in elems.
|
|
||||||
func Contains[T comparable](elems []T, v T) bool {
|
|
||||||
for _, s := range elems {
|
|
||||||
if v == s {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove removes an element from a string slice and
|
// Remove removes an element from a string slice and
|
||||||
// returns the modified slice
|
// returns the modified slice
|
||||||
func Remove(elems []string, val string) []string {
|
func Remove(elems []string, val string) []string {
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -34,7 +35,6 @@ import (
|
||||||
"github.com/sftpgo/sdk"
|
"github.com/sftpgo/sdk"
|
||||||
|
|
||||||
"github.com/drakkan/sftpgo/v2/internal/logger"
|
"github.com/drakkan/sftpgo/v2/internal/logger"
|
||||||
"github.com/drakkan/sftpgo/v2/internal/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -475,7 +475,7 @@ func (fs *OsFs) findNonexistentDirs(filePath string) ([]string, error) {
|
||||||
for fs.IsNotExist(err) {
|
for fs.IsNotExist(err) {
|
||||||
results = append(results, parent)
|
results = append(results, parent)
|
||||||
parent = filepath.Dir(parent)
|
parent = filepath.Dir(parent)
|
||||||
if util.Contains(results, parent) {
|
if slices.Contains(results, parent) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
_, err = os.Stat(parent)
|
_, err = os.Stat(parent)
|
||||||
|
|
|
@ -30,6 +30,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -161,7 +162,7 @@ func (fs *S3Fs) Stat(name string) (os.FileInfo, error) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Some S3 providers (like SeaweedFS) remove the trailing '/' from object keys.
|
// Some S3 providers (like SeaweedFS) remove the trailing '/' from object keys.
|
||||||
// So we check some common content types to detect if this is a "directory".
|
// So we check some common content types to detect if this is a "directory".
|
||||||
isDir := util.Contains(s3DirMimeTypes, util.GetStringFromPointer(obj.ContentType))
|
isDir := slices.Contains(s3DirMimeTypes, util.GetStringFromPointer(obj.ContentType))
|
||||||
if util.GetIntFromPointer(obj.ContentLength) == 0 && !isDir {
|
if util.GetIntFromPointer(obj.ContentLength) == 0 && !isDir {
|
||||||
_, err = fs.headObject(name + "/")
|
_, err = fs.headObject(name + "/")
|
||||||
isDir = err == nil
|
isDir = err == nil
|
||||||
|
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -125,7 +126,7 @@ func (c *SFTPFsConfig) isEqual(other SFTPFsConfig) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, fp := range c.Fingerprints {
|
for _, fp := range c.Fingerprints {
|
||||||
if !util.Contains(other.Fingerprints, fp) {
|
if !slices.Contains(other.Fingerprints, fp) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -954,12 +955,12 @@ func (c *sftpConnection) openConnNoLock() error {
|
||||||
User: c.config.Username,
|
User: c.config.Username,
|
||||||
HostKeyCallback: func(_ string, _ net.Addr, key ssh.PublicKey) error {
|
HostKeyCallback: func(_ string, _ net.Addr, key ssh.PublicKey) error {
|
||||||
fp := ssh.FingerprintSHA256(key)
|
fp := ssh.FingerprintSHA256(key)
|
||||||
if util.Contains(sftpFingerprints, fp) {
|
if slices.Contains(sftpFingerprints, fp) {
|
||||||
if allowSelfConnections == 0 {
|
if allowSelfConnections == 0 {
|
||||||
logger.Log(logger.LevelError, c.logSender, "", "SFTP self connections not allowed")
|
logger.Log(logger.LevelError, c.logSender, "", "SFTP self connections not allowed")
|
||||||
return ErrSFTPLoop
|
return ErrSFTPLoop
|
||||||
}
|
}
|
||||||
if util.Contains(c.config.forbiddenSelfUsernames, c.config.Username) {
|
if slices.Contains(c.config.forbiddenSelfUsernames, c.config.Username) {
|
||||||
logger.Log(logger.LevelError, c.logSender, "",
|
logger.Log(logger.LevelError, c.logSender, "",
|
||||||
"SFTP loop or nested local SFTP folders detected, username %q, forbidden usernames: %+v",
|
"SFTP loop or nested local SFTP folders detected, username %q, forbidden usernames: %+v",
|
||||||
c.config.Username, c.config.forbiddenSelfUsernames)
|
c.config.Username, c.config.forbiddenSelfUsernames)
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -764,7 +765,7 @@ func (c *AzBlobFsConfig) validate() error {
|
||||||
if err := c.checkPartSizeAndConcurrency(); err != nil {
|
if err := c.checkPartSizeAndConcurrency(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !util.Contains(validAzAccessTier, c.AccessTier) {
|
if !slices.Contains(validAzAccessTier, c.AccessTier) {
|
||||||
return fmt.Errorf("invalid access tier %q, valid values: \"''%v\"", c.AccessTier, strings.Join(validAzAccessTier, ", "))
|
return fmt.Errorf("invalid access tier %q, valid values: \"''%v\"", c.AccessTier, strings.Join(validAzAccessTier, ", "))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"slices"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -447,7 +448,7 @@ func (f *webDavFile) Patch(patches []webdav.Proppatch) ([]webdav.Propstat, error
|
||||||
pstat := webdav.Propstat{}
|
pstat := webdav.Propstat{}
|
||||||
for _, p := range patch.Props {
|
for _, p := range patch.Props {
|
||||||
if status == http.StatusForbidden && !hasError {
|
if status == http.StatusForbidden && !hasError {
|
||||||
if !patch.Remove && util.Contains(lastModifiedProps, p.XMLName.Local) {
|
if !patch.Remove && slices.Contains(lastModifiedProps, p.XMLName.Local) {
|
||||||
parsed, err := parseTime(util.BytesToString(p.InnerXML))
|
parsed, err := parseTime(util.BytesToString(p.InnerXML))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.Connection.Log(logger.LevelWarn, "unsupported last modification time: %q, err: %v",
|
f.Connection.Log(logger.LevelWarn, "unsupported last modification time: %q, err: %v",
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -346,7 +347,7 @@ func (s *webDavServer) validateUser(user *dataprovider.User, r *http.Request, lo
|
||||||
user.Username, user.HomeDir)
|
user.Username, user.HomeDir)
|
||||||
return connID, fmt.Errorf("cannot login user with invalid home dir: %q", user.HomeDir)
|
return connID, fmt.Errorf("cannot login user with invalid home dir: %q", user.HomeDir)
|
||||||
}
|
}
|
||||||
if util.Contains(user.Filters.DeniedProtocols, common.ProtocolWebDAV) {
|
if slices.Contains(user.Filters.DeniedProtocols, common.ProtocolWebDAV) {
|
||||||
logger.Info(logSender, connectionID, "cannot login user %q, protocol DAV is not allowed", user.Username)
|
logger.Info(logSender, connectionID, "cannot login user %q, protocol DAV is not allowed", user.Username)
|
||||||
return connID, fmt.Errorf("protocol DAV is not allowed for user %q", user.Username)
|
return connID, fmt.Errorf("protocol DAV is not allowed for user %q", user.Username)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue