diff --git a/internal/acme/acme.go b/internal/acme/acme.go index 409ef260..b2950972 100644 --- a/internal/acme/acme.go +++ b/internal/acme/acme.go @@ -30,6 +30,7 @@ import ( "net/url" "os" "path/filepath" + "slices" "strconv" "strings" "time" @@ -249,7 +250,7 @@ func (c *Configuration) Initialize(configDir string) error { if c.RenewDays < 1 { 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) } caURL, err := url.Parse(c.CAEndpoint) diff --git a/internal/command/command.go b/internal/command/command.go index b3d56aa3..fcf2da1f 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -17,10 +17,9 @@ package command import ( "fmt" + "slices" "strings" "time" - - "github.com/drakkan/sftpgo/v2/internal/util" ) const ( @@ -117,7 +116,7 @@ func (c Config) Initialize() error { } // don't validate args, we allow to pass empty arguments 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) } } diff --git a/internal/common/actions.go b/internal/common/actions.go index 446068c1..2f8c39f2 100644 --- a/internal/common/actions.go +++ b/internal/common/actions.go @@ -25,6 +25,7 @@ import ( "os/exec" "path" "path/filepath" + "slices" "strings" "sync/atomic" "time" @@ -86,7 +87,7 @@ func InitializeActionHandler(handler ActionHandler) { func ExecutePreAction(conn *BaseConnection, operation, filePath, virtualPath string, fileSize int64, openFlags int) (int, error) { var event *notifier.FsEvent hasNotifiersPlugin := plugin.Handler.HasNotifiers() - hasHook := util.Contains(Config.Actions.ExecuteOn, operation) + hasHook := slices.Contains(Config.Actions.ExecuteOn, operation) hasRules := eventManager.hasFsRules() if !hasHook && !hasNotifiersPlugin && !hasRules { return 0, nil @@ -132,7 +133,7 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua fileSize int64, err error, elapsed int64, metadata map[string]string, ) error { hasNotifiersPlugin := plugin.Handler.HasNotifiers() - hasHook := util.Contains(Config.Actions.ExecuteOn, operation) + hasHook := slices.Contains(Config.Actions.ExecuteOn, operation) hasRules := eventManager.hasFsRules() if !hasHook && !hasNotifiersPlugin && !hasRules { return nil @@ -173,7 +174,7 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua } } if hasHook { - if util.Contains(Config.Actions.ExecuteSync, operation) { + if slices.Contains(Config.Actions.ExecuteSync, operation) { _, err := actionHandler.Handle(notification) return err } @@ -247,7 +248,7 @@ func newActionNotification( type defaultActionHandler struct{} 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 } diff --git a/internal/common/common.go b/internal/common/common.go index a53d707e..3ef02819 100644 --- a/internal/common/common.go +++ b/internal/common/common.go @@ -25,6 +25,7 @@ import ( "os" "os/exec" "path/filepath" + "slices" "strconv" "strings" "sync" @@ -207,7 +208,7 @@ func Initialize(c Configuration, isShared int) error { Config.rateLimitersList = rateLimitersList } 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) } var defender Defender @@ -777,7 +778,7 @@ func (c *Configuration) checkPostDisconnectHook(remoteAddr, protocol, username, if c.PostDisconnectHook == "" { return } - if !util.Contains(disconnHookProtocols, protocol) { + if !slices.Contains(disconnHookProtocols, protocol) { return } go c.executePostDisconnectHook(remoteAddr, protocol, username, connID, connectionTime) @@ -1019,7 +1020,7 @@ func (conns *ActiveConnections) Remove(connectionID string) { 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", 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()) logger.ConnectionFailedLog("", ip, dataprovider.LoginMethodNoAuthTried, ProtocolFTP, dataprovider.ErrNoAuthTried.Error()) diff --git a/internal/common/common_test.go b/internal/common/common_test.go index adcac1db..0b2df639 100644 --- a/internal/common/common_test.go +++ b/internal/common/common_test.go @@ -23,6 +23,7 @@ import ( "os/exec" "path/filepath" "runtime" + "slices" "sync" "testing" "time" @@ -1226,8 +1227,8 @@ func TestFolderCopy(t *testing.T) { folder.ID = 2 folder.Users = []string{"user3"} require.Len(t, folderCopy.Users, 2) - require.True(t, util.Contains(folderCopy.Users, "user1")) - require.True(t, util.Contains(folderCopy.Users, "user2")) + require.True(t, slices.Contains(folderCopy.Users, "user1")) + require.True(t, slices.Contains(folderCopy.Users, "user2")) require.Equal(t, int64(1), folderCopy.ID) require.Equal(t, folder.Name, folderCopy.Name) require.Equal(t, folder.MappedPath, folderCopy.MappedPath) @@ -1243,7 +1244,7 @@ func TestFolderCopy(t *testing.T) { folderCopy = folder.GetACopy() folder.FsConfig.CryptConfig.Passphrase = kms.NewEmptySecret() 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, folder.Name, folderCopy.Name) require.Equal(t, folder.MappedPath, folderCopy.MappedPath) diff --git a/internal/common/connection.go b/internal/common/connection.go index 99efc1a5..144ecaaa 100644 --- a/internal/common/connection.go +++ b/internal/common/connection.go @@ -63,7 +63,7 @@ type BaseConnection struct { // NewBaseConnection returns a new BaseConnection func NewBaseConnection(id, protocol, localAddr, remoteAddr string, user dataprovider.User) *BaseConnection { connID := id - if util.Contains(supportedProtocols, protocol) { + if slices.Contains(supportedProtocols, protocol) { connID = fmt.Sprintf("%s_%s", protocol, id) } 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 func (c *BaseConnection) SetProtocol(protocol string) { 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) } } diff --git a/internal/common/connection_test.go b/internal/common/connection_test.go index 06b3fa4b..5f76049f 100644 --- a/internal/common/connection_test.go +++ b/internal/common/connection_test.go @@ -22,6 +22,7 @@ import ( "path" "path/filepath" "runtime" + "slices" "strconv" "testing" "time" @@ -389,7 +390,7 @@ func TestErrorsMapping(t *testing.T) { err := conn.GetFsError(fs, os.ErrNotExist) if protocol == ProtocolSFTP { 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()) } else { assert.EqualError(t, err, ErrNotExist.Error()) diff --git a/internal/common/eventmanager.go b/internal/common/eventmanager.go index 4bc33635..aac96ea3 100644 --- a/internal/common/eventmanager.go +++ b/internal/common/eventmanager.go @@ -31,6 +31,7 @@ import ( "os/exec" "path" "path/filepath" + "slices" "strconv" "strings" "sync" @@ -307,7 +308,7 @@ func (*eventRulesContainer) checkIPDLoginEventMatch(conditions *dataprovider.Eve } 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 } if !checkEventConditionPatterns(params.Name, conditions.Options.Names) { @@ -316,14 +317,14 @@ func (*eventRulesContainer) checkProviderEventMatch(conditions *dataprovider.Eve if !checkEventConditionPatterns(params.Role, conditions.Options.RoleNames) { 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 true } 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 } if !checkEventConditionPatterns(params.Name, conditions.Options.Names) { @@ -338,7 +339,7 @@ func (*eventRulesContainer) checkFsEventMatch(conditions *dataprovider.EventCond if !checkEventConditionPatterns(params.VirtualPath, conditions.Options.FsPaths) { 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 } if params.Event == operationUpload || params.Event == operationDownload { diff --git a/internal/common/protocol_test.go b/internal/common/protocol_test.go index 82d983b7..350ae4b3 100644 --- a/internal/common/protocol_test.go +++ b/internal/common/protocol_test.go @@ -30,6 +30,7 @@ import ( "path" "path/filepath" "runtime" + "slices" "strings" "sync" "testing" @@ -3978,9 +3979,9 @@ func TestEventRule(t *testing.T) { }, 3000*time.Millisecond, 100*time.Millisecond) email := lastReceivedEmail.get() assert.Len(t, email.To, 3) - assert.True(t, util.Contains(email.To, "test1@example.com")) - assert.True(t, util.Contains(email.To, "test2@example.com")) - assert.True(t, util.Contains(email.To, "test3@example.com")) + assert.True(t, slices.Contains(email.To, "test1@example.com")) + assert.True(t, slices.Contains(email.To, "test2@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)) // 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) @@ -3999,9 +4000,9 @@ func TestEventRule(t *testing.T) { }, 3000*time.Millisecond, 100*time.Millisecond) email = lastReceivedEmail.get() assert.Len(t, email.To, 3) - assert.True(t, util.Contains(email.To, "test1@example.com")) - assert.True(t, util.Contains(email.To, "test2@example.com")) - assert.True(t, util.Contains(email.To, "test3@example.com")) + assert.True(t, slices.Contains(email.To, "test1@example.com")) + assert.True(t, slices.Contains(email.To, "test2@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, `"download" failed`) assert.Contains(t, email.Data, common.ErrReadQuotaExceeded.Error()) @@ -4019,7 +4020,7 @@ func TestEventRule(t *testing.T) { }, 3000*time.Millisecond, 100*time.Millisecond) email = lastReceivedEmail.get() 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(`action %q failed`, action1.Name)) // now test the download rule @@ -4036,9 +4037,9 @@ func TestEventRule(t *testing.T) { }, 3000*time.Millisecond, 100*time.Millisecond) email = lastReceivedEmail.get() assert.Len(t, email.To, 3) - assert.True(t, util.Contains(email.To, "test1@example.com")) - assert.True(t, util.Contains(email.To, "test2@example.com")) - assert.True(t, util.Contains(email.To, "test3@example.com")) + assert.True(t, slices.Contains(email.To, "test1@example.com")) + assert.True(t, slices.Contains(email.To, "test2@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)) } // test upload action command with arguments @@ -4079,9 +4080,9 @@ func TestEventRule(t *testing.T) { }, 3000*time.Millisecond, 100*time.Millisecond) email := lastReceivedEmail.get() assert.Len(t, email.To, 3) - assert.True(t, util.Contains(email.To, "test1@example.com")) - assert.True(t, util.Contains(email.To, "test2@example.com")) - assert.True(t, util.Contains(email.To, "test3@example.com")) + assert.True(t, slices.Contains(email.To, "test1@example.com")) + assert.True(t, slices.Contains(email.To, "test2@example.com")) + assert.True(t, slices.Contains(email.To, "test3@example.com")) assert.Contains(t, email.Data, `Subject: New "delete" from "admin"`) _, err = httpdtest.RemoveEventRule(rule3, http.StatusOK) assert.NoError(t, err) @@ -4236,7 +4237,7 @@ func TestEventRuleProviderEvents(t *testing.T) { }, 3000*time.Millisecond, 100*time.Millisecond) email := lastReceivedEmail.get() 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"`) } // now delete the script to generate an error @@ -4251,7 +4252,7 @@ func TestEventRuleProviderEvents(t *testing.T) { }, 3000*time.Millisecond, 100*time.Millisecond) email := lastReceivedEmail.get() 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, fmt.Sprintf("Object name: %s object type: folder", folder.Name)) lastReceivedEmail.reset() @@ -5306,7 +5307,7 @@ func TestBackupAsAttachment(t *testing.T) { }, 3000*time.Millisecond, 100*time.Millisecond) email := lastReceivedEmail.get() 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, `Domain: example.com`) assert.Contains(t, email.Data, "Content-Type: application/json") @@ -5676,7 +5677,7 @@ func TestEventActionCompressQuotaErrors(t *testing.T) { }, 3*time.Second, 100*time.Millisecond) email := lastReceivedEmail.get() 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, common.ErrQuotaExceeded.Error()) // update quota size so the user is already overquota @@ -5691,7 +5692,7 @@ func TestEventActionCompressQuotaErrors(t *testing.T) { }, 3*time.Second, 100*time.Millisecond) email = lastReceivedEmail.get() 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, common.ErrQuotaExceeded.Error()) // 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) email = lastReceivedEmail.get() 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, "unable to estimate archive size") } @@ -6041,7 +6042,7 @@ func TestEventActionEmailAttachments(t *testing.T) { }, 1500*time.Millisecond, 100*time.Millisecond) email := lastReceivedEmail.get() 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, "Content-Disposition: attachment") } @@ -6218,7 +6219,7 @@ func TestEventActionsRetentionReports(t *testing.T) { email := lastReceivedEmail.get() 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, "Content-Disposition: attachment") _, err = client.Stat(testDir) @@ -6391,7 +6392,7 @@ func TestEventRuleFirstUploadDownloadActions(t *testing.T) { }, 1500*time.Millisecond, 100*time.Millisecond) email := lastReceivedEmail.get() 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)) lastReceivedEmail.reset() // a new upload will not produce a new notification @@ -6414,7 +6415,7 @@ func TestEventRuleFirstUploadDownloadActions(t *testing.T) { }, 1500*time.Millisecond, 100*time.Millisecond) email = lastReceivedEmail.get() 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)) // download again lastReceivedEmail.reset() @@ -6510,7 +6511,7 @@ func TestEventRuleRenameEvent(t *testing.T) { }, 1500*time.Millisecond, 100*time.Millisecond) email := lastReceivedEmail.get() 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, "Content-Type: text/html") 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) email := lastReceivedEmail.get() 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, username) assert.Contains(t, email.Data, custom1) @@ -6708,7 +6709,7 @@ func TestEventRuleIDPLogin(t *testing.T) { }, 3000*time.Millisecond, 100*time.Millisecond) email = lastReceivedEmail.get() 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, username) assert.Contains(t, email.Data, custom1) @@ -6900,7 +6901,7 @@ func TestEventRuleEmailField(t *testing.T) { }, 3000*time.Millisecond, 100*time.Millisecond) email := lastReceivedEmail.get() 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"`) // 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) email = lastReceivedEmail.get() 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`) conn, client, err := getSftpClient(user) @@ -6931,7 +6932,7 @@ func TestEventRuleEmailField(t *testing.T) { }, 3000*time.Millisecond, 100*time.Millisecond) email := lastReceivedEmail.get() 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)) } @@ -7038,7 +7039,7 @@ func TestEventRuleCertificate(t *testing.T) { }, 3000*time.Millisecond, 100*time.Millisecond) email := lastReceivedEmail.get() 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, "Content-Type: text/plain") assert.Contains(t, email.Data, `Domain: example.com Timestamp`) @@ -7058,7 +7059,7 @@ func TestEventRuleCertificate(t *testing.T) { }, 3000*time.Millisecond, 100*time.Millisecond) email = lastReceivedEmail.get() 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, `Domain: example.com Timestamp`) assert.Contains(t, email.Data, errRenew.Error()) @@ -7184,8 +7185,8 @@ func TestEventRuleIPBlocked(t *testing.T) { }, 3000*time.Millisecond, 100*time.Millisecond) email := lastReceivedEmail.get() assert.Len(t, email.To, 2) - assert.True(t, util.Contains(email.To, "test3@example.com")) - assert.True(t, util.Contains(email.To, "test4@example.com")) + assert.True(t, slices.Contains(email.To, "test3@example.com")) + assert.True(t, slices.Contains(email.To, "test4@example.com")) assert.Contains(t, email.Data, `Subject: New "IP Blocked"`) err = dataprovider.DeleteEventRule(rule1.Name, "", "", "") @@ -8357,7 +8358,7 @@ func TestSFTPLoopError(t *testing.T) { }, 3000*time.Millisecond, 100*time.Millisecond) email := lastReceivedEmail.get() 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`) user1.VirtualFolders[0].FsConfig.SFTPConfig.Password = kms.NewPlainSecret(defaultPassword) diff --git a/internal/common/ratelimiter.go b/internal/common/ratelimiter.go index 1caa2ebf..85d88092 100644 --- a/internal/common/ratelimiter.go +++ b/internal/common/ratelimiter.go @@ -17,6 +17,7 @@ package common import ( "errors" "fmt" + "slices" "sort" "sync" "sync/atomic" @@ -94,7 +95,7 @@ func (r *RateLimiterConfig) validate() error { } r.Protocols = util.RemoveDuplicates(r.Protocols, true) for _, protocol := range r.Protocols { - if !util.Contains(rateLimiterProtocolValues, protocol) { + if !slices.Contains(rateLimiterProtocolValues, protocol) { return fmt.Errorf("invalid protocol %q", protocol) } } diff --git a/internal/common/tlsutils.go b/internal/common/tlsutils.go index c604728d..a7dfb0c6 100644 --- a/internal/common/tlsutils.go +++ b/internal/common/tlsutils.go @@ -25,6 +25,7 @@ import ( "math/rand" "os" "path/filepath" + "slices" "sync" "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) 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) } } @@ -190,7 +191,7 @@ func (m *CertManager) LoadCRLs() error { logger.Debug(m.logSender, "", "CRL %q successfully loaded", revocationList) crls = append(crls, crl) - if !util.Contains(m.monitorList, revocationList) { + if !slices.Contains(m.monitorList, revocationList) { m.monitorList = append(m.monitorList, revocationList) } } diff --git a/internal/config/config.go b/internal/config/config.go index a85c8777..db83d551 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -20,6 +20,7 @@ import ( "fmt" "os" "path/filepath" + "slices" "strconv" "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 } } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index fc6d9f98..ced41bd5 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -19,6 +19,7 @@ import ( "encoding/json" "os" "path/filepath" + "slices" "testing" "github.com/sftpgo/sdk/kms" @@ -36,7 +37,6 @@ import ( "github.com/drakkan/sftpgo/v2/internal/plugin" "github.com/drakkan/sftpgo/v2/internal/sftpd" "github.com/drakkan/sftpgo/v2/internal/smtp" - "github.com/drakkan/sftpgo/v2/internal/util" "github.com/drakkan/sftpgo/v2/internal/webdavd" ) @@ -679,8 +679,8 @@ func TestPluginsFromEnv(t *testing.T) { pluginConf := pluginsConf[0] require.Equal(t, "notifier", pluginConf.Type) require.Len(t, pluginConf.NotifierOptions.FsEvents, 2) - require.True(t, util.Contains(pluginConf.NotifierOptions.FsEvents, "upload")) - require.True(t, util.Contains(pluginConf.NotifierOptions.FsEvents, "download")) + require.True(t, slices.Contains(pluginConf.NotifierOptions.FsEvents, "upload")) + require.True(t, slices.Contains(pluginConf.NotifierOptions.FsEvents, "download")) require.Len(t, pluginConf.NotifierOptions.ProviderEvents, 2) require.Equal(t, "add", pluginConf.NotifierOptions.ProviderEvents[0]) require.Equal(t, "update", pluginConf.NotifierOptions.ProviderEvents[1]) @@ -729,8 +729,8 @@ func TestPluginsFromEnv(t *testing.T) { pluginConf = pluginsConf[0] require.Equal(t, "notifier", pluginConf.Type) require.Len(t, pluginConf.NotifierOptions.FsEvents, 2) - require.True(t, util.Contains(pluginConf.NotifierOptions.FsEvents, "upload")) - require.True(t, util.Contains(pluginConf.NotifierOptions.FsEvents, "download")) + require.True(t, slices.Contains(pluginConf.NotifierOptions.FsEvents, "upload")) + require.True(t, slices.Contains(pluginConf.NotifierOptions.FsEvents, "download")) require.Len(t, pluginConf.NotifierOptions.ProviderEvents, 2) require.Equal(t, "add", pluginConf.NotifierOptions.ProviderEvents[0]) require.Equal(t, "update", pluginConf.NotifierOptions.ProviderEvents[1]) @@ -787,8 +787,8 @@ func TestRateLimitersFromEnv(t *testing.T) { require.Equal(t, 2, limiters[0].Type) protocols := limiters[0].Protocols require.Len(t, protocols, 2) - require.True(t, util.Contains(protocols, common.ProtocolFTP)) - require.True(t, util.Contains(protocols, common.ProtocolSSH)) + require.True(t, slices.Contains(protocols, common.ProtocolFTP)) + require.True(t, slices.Contains(protocols, common.ProtocolSSH)) require.True(t, limiters[0].GenerateDefenderEvents) require.Equal(t, 50, limiters[0].EntriesSoftLimit) require.Equal(t, 100, limiters[0].EntriesHardLimit) @@ -799,10 +799,10 @@ func TestRateLimitersFromEnv(t *testing.T) { require.Equal(t, 2, limiters[1].Type) protocols = limiters[1].Protocols require.Len(t, protocols, 4) - require.True(t, util.Contains(protocols, common.ProtocolFTP)) - require.True(t, util.Contains(protocols, common.ProtocolSSH)) - require.True(t, util.Contains(protocols, common.ProtocolWebDAV)) - require.True(t, util.Contains(protocols, common.ProtocolHTTP)) + require.True(t, slices.Contains(protocols, common.ProtocolFTP)) + require.True(t, slices.Contains(protocols, common.ProtocolSSH)) + require.True(t, slices.Contains(protocols, common.ProtocolWebDAV)) + require.True(t, slices.Contains(protocols, common.ProtocolHTTP)) require.False(t, limiters[1].GenerateDefenderEvents) require.Equal(t, 100, limiters[1].EntriesSoftLimit) require.Equal(t, 150, limiters[1].EntriesHardLimit) diff --git a/internal/dataprovider/actions.go b/internal/dataprovider/actions.go index 066b7bf7..5341e0b1 100644 --- a/internal/dataprovider/actions.go +++ b/internal/dataprovider/actions.go @@ -21,6 +21,7 @@ import ( "net/url" "os/exec" "path/filepath" + "slices" "strings" "time" @@ -78,8 +79,8 @@ func executeAction(operation, executor, ip, objectType, objectName, role string, if config.Actions.Hook == "" { return } - if !util.Contains(config.Actions.ExecuteOn, operation) || - !util.Contains(config.Actions.ExecuteFor, objectType) { + if !slices.Contains(config.Actions.ExecuteOn, operation) || + !slices.Contains(config.Actions.ExecuteFor, objectType) { return } diff --git a/internal/dataprovider/admin.go b/internal/dataprovider/admin.go index b88cbe00..888cc21e 100644 --- a/internal/dataprovider/admin.go +++ b/internal/dataprovider/admin.go @@ -20,6 +20,7 @@ import ( "fmt" "net" "os" + "slices" "strconv" "strings" @@ -96,7 +97,7 @@ func (c *AdminTOTPConfig) validate(username string) error { if c.ConfigName == "" { 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)) } if c.Secret.IsEmpty() { @@ -337,15 +338,15 @@ func (a *Admin) validatePermissions() error { util.I18nErrorPermissionsRequired, ) } - if util.Contains(a.Permissions, PermAdminAny) { + if slices.Contains(a.Permissions, PermAdminAny) { a.Permissions = []string{PermAdminAny} } for _, perm := range a.Permissions { - if !util.Contains(validAdminPerms, perm) { + if !slices.Contains(validAdminPerms, perm) { return util.NewValidationError(fmt.Sprintf("invalid permission: %q", perm)) } if a.Role != "" { - if util.Contains(forbiddenPermsForRoleAdmins, perm) { + if slices.Contains(forbiddenPermsForRoleAdmins, perm) { deniedPerms := strings.Join(forbiddenPermsForRoleAdmins, ",") return util.NewI18nError( 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 func (a *Admin) HasPermission(perm string) bool { - if util.Contains(a.Permissions, PermAdminAny) { + if slices.Contains(a.Permissions, PermAdminAny) { return true } - return util.Contains(a.Permissions, perm) + return slices.Contains(a.Permissions, perm) } // GetAllowedIPAsString returns the allowed IP as comma separated string diff --git a/internal/dataprovider/bolt.go b/internal/dataprovider/bolt.go index 59b9d095..56be50ea 100644 --- a/internal/dataprovider/bolt.go +++ b/internal/dataprovider/bolt.go @@ -25,6 +25,7 @@ import ( "fmt" "net/netip" "path/filepath" + "slices" "sort" "time" @@ -3320,7 +3321,7 @@ func (p *BoltProvider) addAdminToRole(username, roleName string, bucket *bolt.Bu if err != nil { return err } - if !util.Contains(role.Admins, username) { + if !slices.Contains(role.Admins, username) { role.Admins = append(role.Admins, username) buf, err := json.Marshal(role) if err != nil { @@ -3345,7 +3346,7 @@ func (p *BoltProvider) removeAdminFromRole(username, roleName string, bucket *bo if err != nil { return err } - if util.Contains(role.Admins, username) { + if slices.Contains(role.Admins, username) { var admins []string for _, admin := range role.Admins { if admin != username { @@ -3375,7 +3376,7 @@ func (p *BoltProvider) addUserToRole(username, roleName string, bucket *bolt.Buc if err != nil { return err } - if !util.Contains(role.Users, username) { + if !slices.Contains(role.Users, username) { role.Users = append(role.Users, username) buf, err := json.Marshal(role) if err != nil { @@ -3400,7 +3401,7 @@ func (p *BoltProvider) removeUserFromRole(username, roleName string, bucket *bol if err != nil { return err } - if util.Contains(role.Users, username) { + if slices.Contains(role.Users, username) { var users []string for _, user := range role.Users { if user != username { @@ -3428,7 +3429,7 @@ func (p *BoltProvider) addRuleToActionMapping(ruleName, actionName string, bucke if err != nil { return err } - if !util.Contains(action.Rules, ruleName) { + if !slices.Contains(action.Rules, ruleName) { action.Rules = append(action.Rules, ruleName) buf, err := json.Marshal(action) if err != nil { @@ -3450,7 +3451,7 @@ func (p *BoltProvider) removeRuleFromActionMapping(ruleName, actionName string, if err != nil { return err } - if util.Contains(action.Rules, ruleName) { + if slices.Contains(action.Rules, ruleName) { var rules []string for _, r := range action.Rules { if r != ruleName { @@ -3477,7 +3478,7 @@ func (p *BoltProvider) addUserToGroupMapping(username, groupname string, bucket if err != nil { return err } - if !util.Contains(group.Users, username) { + if !slices.Contains(group.Users, username) { group.Users = append(group.Users, username) buf, err := json.Marshal(group) if err != nil { @@ -3522,7 +3523,7 @@ func (p *BoltProvider) addAdminToGroupMapping(username, groupname string, bucket if err != nil { return err } - if !util.Contains(group.Admins, username) { + if !slices.Contains(group.Admins, username) { group.Admins = append(group.Admins, username) buf, err := json.Marshal(group) if err != nil { @@ -3593,11 +3594,11 @@ func (p *BoltProvider) addRelationToFolderMapping(folderName string, user *User, return err } 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) 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) updated = true } diff --git a/internal/dataprovider/configs.go b/internal/dataprovider/configs.go index cb3f1099..74862700 100644 --- a/internal/dataprovider/configs.go +++ b/internal/dataprovider/configs.go @@ -20,6 +20,7 @@ import ( "fmt" "image/png" "net/url" + "slices" "golang.org/x/crypto/ssh" @@ -105,7 +106,7 @@ func (c *SFTPDConfigs) validate() error { if algo == ssh.CertAlgoRSAv01 { continue } - if !util.Contains(supportedHostKeyAlgos, algo) { + if !slices.Contains(supportedHostKeyAlgos, algo) { return util.NewValidationError(fmt.Sprintf("unsupported host key algorithm %q", algo)) } hostKeyAlgos = append(hostKeyAlgos, algo) @@ -116,24 +117,24 @@ func (c *SFTPDConfigs) validate() error { if algo == "diffie-hellman-group18-sha512" || algo == ssh.KeyExchangeDHGEXSHA256 { continue } - if !util.Contains(supportedKexAlgos, algo) { + if !slices.Contains(supportedKexAlgos, algo) { return util.NewValidationError(fmt.Sprintf("unsupported KEX algorithm %q", algo)) } kexAlgos = append(kexAlgos, algo) } c.KexAlgorithms = kexAlgos for _, cipher := range c.Ciphers { - if !util.Contains(supportedCiphers, cipher) { + if !slices.Contains(supportedCiphers, cipher) { return util.NewValidationError(fmt.Sprintf("unsupported cipher %q", cipher)) } } 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)) } } 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)) } } diff --git a/internal/dataprovider/dataprovider.go b/internal/dataprovider/dataprovider.go index e06474d8..24c8171b 100644 --- a/internal/dataprovider/dataprovider.go +++ b/internal/dataprovider/dataprovider.go @@ -44,6 +44,7 @@ import ( "path/filepath" "regexp" "runtime" + "slices" "strconv" "strings" "sync" @@ -519,7 +520,7 @@ type Config struct { // GetShared returns the provider share mode. // This method is called before the provider is initialized func (c *Config) GetShared() int { - if !util.Contains(sharedProviders, c.Driver) { + if !slices.Contains(sharedProviders, c.Driver) { return 0 } return c.IsShared @@ -885,7 +886,7 @@ func SetTempPath(fsPath string) { } func checkSharedMode() { - if !util.Contains(sharedProviders, config.Driver) { + if !slices.Contains(sharedProviders, config.Driver) { 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 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 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 { - if len(scopes) == 0 || util.Contains(scopes, DumpScopeUsers) { + if len(scopes) == 0 || slices.Contains(scopes, DumpScopeUsers) { users, err := provider.dumpUsers() if err != nil { return err @@ -2384,7 +2385,7 @@ func dumpUsers(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() if err != nil { return err @@ -2395,7 +2396,7 @@ func dumpFolders(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() if err != nil { return err @@ -2406,7 +2407,7 @@ func dumpGroups(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() if err != nil { return err @@ -2417,7 +2418,7 @@ func dumpAdmins(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() if err != nil { return err @@ -2428,7 +2429,7 @@ func dumpAPIKeys(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() if err != nil { return err @@ -2439,7 +2440,7 @@ func dumpShares(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() if err != nil { return err @@ -2450,7 +2451,7 @@ func dumpActions(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() if err != nil { return err @@ -2461,7 +2462,7 @@ func dumpRules(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() if err != nil { return err @@ -2472,7 +2473,7 @@ func dumpRoles(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() if err != nil { return err @@ -2483,7 +2484,7 @@ func dumpIPLists(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() if err != nil { return err @@ -2787,7 +2788,7 @@ func validateUserTOTPConfig(c *UserTOTPConfig, username string) error { if c.ConfigName == "" { 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)) } if c.Secret.IsEmpty() { @@ -2803,7 +2804,7 @@ func validateUserTOTPConfig(c *UserTOTPConfig, username string) error { return util.NewValidationError("totp: specify at least one protocol") } 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)) } } @@ -2836,7 +2837,7 @@ func validateUserPermissions(permsToCheck map[string][]string) (map[string][]str return permissions, util.NewValidationError("invalid permissions") } for _, p := range perms { - if !util.Contains(ValidPerms, p) { + if !slices.Contains(ValidPerms, 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 == "/" { 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} } else { permissions[cleanedDir] = util.RemoveDuplicates(perms, false) @@ -2926,7 +2927,7 @@ func validateFiltersPatternExtensions(baseFilters *sdk.BaseUserFilters) error { util.I18nErrorFilePatternPathInvalid, ) } - if util.Contains(filteredPaths, cleanedPath) { + if slices.Contains(filteredPaths, cleanedPath) { return util.NewI18nError( util.NewValidationError(fmt.Sprintf("duplicate file patterns filter for path %q", f.Path)), util.I18nErrorFilePatternDuplicated, @@ -3045,13 +3046,13 @@ func validateFilterProtocols(filters *sdk.BaseUserFilters) error { return util.NewValidationError("invalid denied_protocols") } 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)) } } 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)) } } @@ -3107,7 +3108,7 @@ func validateBaseFilters(filters *sdk.BaseUserFilters) error { return util.NewValidationError("invalid denied_login_methods") } 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)) } } @@ -3115,7 +3116,7 @@ func validateBaseFilters(filters *sdk.BaseUserFilters) error { return err } 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)) } } @@ -3125,7 +3126,7 @@ func validateBaseFilters(filters *sdk.BaseUserFilters) error { } filters.TLSCerts = certs 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)) } } @@ -3193,19 +3194,19 @@ func validateAccessTimeFilters(filters *sdk.BaseUserFilters) 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( util.NewValidationError("two-factor authentication cannot be disabled for a user with an active configuration"), 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( util.NewValidationError("you cannot require password change and at the same time disallow it"), 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( util.NewValidationError("you cannot require two-factor authentication and at the same time disallow it"), util.I18nError2FAConflict, @@ -3526,7 +3527,7 @@ func checkUserPasscode(user *User, password, protocol string) (string, error) { if user.Filters.TOTPConfig.Enabled { switch protocol { case protocolFTP: - if util.Contains(user.Filters.TOTPConfig.Protocols, protocol) { + if slices.Contains(user.Filters.TOTPConfig.Protocols, protocol) { // the TOTP passcode has six digits pwdLen := len(password) if pwdLen < 7 { @@ -3732,7 +3733,7 @@ func doBuiltinKeyboardInteractiveAuth(user *User, client ssh.KeyboardInteractive if err := user.LoadAndApplyGroupSettings(); err != nil { 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 { answers, err := client("", "", []string{"Password: "}, []bool{false}) 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) { - 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 } err := user.Filters.TOTPConfig.Secret.TryDecrypt() @@ -3874,7 +3875,7 @@ func getKeyboardInteractiveAnswers(client ssh.KeyboardInteractiveChallenge, resp } if len(answers) == 1 && response.CheckPwd > 0 { 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", user.Username) 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 { - if util.Contains(reservedUsers, username) { + if slices.Contains(reservedUsers, username) { return util.NewValidationError("this username is reserved") } return nil diff --git a/internal/dataprovider/eventrule.go b/internal/dataprovider/eventrule.go index 74e48b83..ad705c34 100644 --- a/internal/dataprovider/eventrule.go +++ b/internal/dataprovider/eventrule.go @@ -23,6 +23,7 @@ import ( "net/http" "path" "path/filepath" + "slices" "strings" "time" @@ -60,7 +61,7 @@ var ( ) func isActionTypeValid(action int) bool { - return util.Contains(supportedEventActions, action) + return slices.Contains(supportedEventActions, action) } func getActionTypeAsString(action int) string { @@ -115,7 +116,7 @@ var ( ) func isEventTriggerValid(trigger int) bool { - return util.Contains(supportedEventTriggers, trigger) + return slices.Contains(supportedEventTriggers, trigger) } func getTriggerTypeAsString(trigger int) string { @@ -169,7 +170,7 @@ var ( ) func isFilesystemActionValid(value int) bool { - return util.Contains(supportedFsActions, value) + return slices.Contains(supportedFsActions, value) } 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)) } } - if !util.Contains(SupportedHTTPActionMethods, c.Method) { + if !slices.Contains(SupportedHTTPActionMethods, c.Method) { return util.NewValidationError(fmt.Sprintf("unsupported HTTP method: %s", c.Method)) } for _, kv := range c.QueryParameters { @@ -1280,7 +1281,7 @@ func (a *EventAction) validateAssociation(trigger int, fsEvents []string) error } if trigger == EventTriggerFsEvent { for _, ev := range fsEvents { - if !util.Contains(allowedSyncFsEvents, ev) { + if !slices.Contains(allowedSyncFsEvents, ev) { return util.NewI18nError( util.NewValidationError("sync execution is only supported for upload and pre-* events"), util.I18nErrorEvSyncUnsupportedFs, @@ -1361,12 +1362,12 @@ func (f *ConditionOptions) validate() error { } 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)) } } 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)) } } @@ -1468,7 +1469,7 @@ func (c *EventConditions) validate(trigger int) error { ) } 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)) } } @@ -1488,7 +1489,7 @@ func (c *EventConditions) validate(trigger int) error { ) } 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)) } } @@ -1537,7 +1538,7 @@ func (c *EventConditions) validate(trigger int) error { c.Options.MinFileSize = 0 c.Options.MaxFileSize = 0 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)) } default: @@ -1690,7 +1691,7 @@ func (r *EventRule) validateMandatorySyncActions() error { return nil } for _, ev := range r.Conditions.FsEvents { - if util.Contains(mandatorySyncFsEvents, ev) { + if slices.Contains(mandatorySyncFsEvents, ev) { return util.NewI18nError( util.NewValidationError(fmt.Sprintf("event %q requires at least a sync action", ev)), util.I18nErrorRuleSyncActionRequired, @@ -1708,7 +1709,7 @@ func (r *EventRule) checkIPBlockedAndCertificateActions() error { ActionTypeDataRetentionCheck, ActionTypeFilesystem, ActionTypePasswordExpirationCheck, ActionTypeUserExpirationCheck} 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", action.Name, getActionTypeAsString(action.Type), getTriggerTypeAsString(r.Trigger)) } @@ -1724,7 +1725,7 @@ func (r *EventRule) checkProviderEventActions(providerObjectType string) error { ActionTypeDataRetentionCheck, ActionTypeFilesystem, ActionTypePasswordExpirationCheck, ActionTypeUserExpirationCheck} 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", action.Name, getActionTypeAsString(action.Type)) } diff --git a/internal/dataprovider/iplist.go b/internal/dataprovider/iplist.go index 739ef472..f24da8e6 100644 --- a/internal/dataprovider/iplist.go +++ b/internal/dataprovider/iplist.go @@ -19,6 +19,7 @@ import ( "fmt" "net" "net/netip" + "slices" "strings" "sync" "sync/atomic" @@ -85,7 +86,7 @@ var ( // CheckIPListType returns an error if the provided IP list type is not valid 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 nil diff --git a/internal/dataprovider/memory.go b/internal/dataprovider/memory.go index 05d72352..9f2424bd 100644 --- a/internal/dataprovider/memory.go +++ b/internal/dataprovider/memory.go @@ -22,6 +22,7 @@ import ( "net/netip" "os" "path/filepath" + "slices" "sort" "sync" "time" @@ -1210,7 +1211,7 @@ func (p *MemoryProvider) addRuleToActionMapping(ruleName, actionName string) err if err != nil { 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) 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) return } - if util.Contains(a.Rules, ruleName) { + if slices.Contains(a.Rules, ruleName) { var rules []string for _, r := range a.Rules { if r != ruleName { @@ -1240,7 +1241,7 @@ func (p *MemoryProvider) addAdminToGroupMapping(username, groupname string) erro if err != nil { return err } - if !util.Contains(g.Admins, username) { + if !slices.Contains(g.Admins, username) { g.Admins = append(g.Admins, username) p.dbHandle.groups[groupname] = g } @@ -1283,7 +1284,7 @@ func (p *MemoryProvider) addUserToGroupMapping(username, groupname string) error if err != nil { return err } - if !util.Contains(g.Users, username) { + if !slices.Contains(g.Users, username) { g.Users = append(g.Users, username) p.dbHandle.groups[groupname] = g } @@ -1313,7 +1314,7 @@ func (p *MemoryProvider) addAdminToRole(username, role string) error { if err != nil { 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) p.dbHandle.roles[role] = r } @@ -1347,7 +1348,7 @@ func (p *MemoryProvider) addUserToRole(username, role string) error { if err != nil { 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) p.dbHandle.roles[role] = r } @@ -1378,7 +1379,7 @@ func (p *MemoryProvider) addUserToFolderMapping(username, foldername string) err if err != nil { 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) p.dbHandle.vfolders[foldername] = f } @@ -1390,7 +1391,7 @@ func (p *MemoryProvider) addGroupToFolderMapping(name, foldername string) error if err != nil { 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) p.dbHandle.vfolders[foldername] = f } diff --git a/internal/dataprovider/pgsql.go b/internal/dataprovider/pgsql.go index 75ddd11f..58000cbe 100644 --- a/internal/dataprovider/pgsql.go +++ b/internal/dataprovider/pgsql.go @@ -24,6 +24,7 @@ import ( "errors" "fmt" "net" + "slices" "strconv" "strings" "time" @@ -305,7 +306,7 @@ func getPGSQLConnectionString(redactedPwd bool) string { if config.DisableSNI { connectionString += " sslsni=0" } - if util.Contains(pgSQLTargetSessionAttrs, config.TargetSessionAttrs) { + if slices.Contains(pgSQLTargetSessionAttrs, config.TargetSessionAttrs) { connectionString += fmt.Sprintf(" target_session_attrs='%s'", config.TargetSessionAttrs) } } else { diff --git a/internal/dataprovider/user.go b/internal/dataprovider/user.go index a6a2f1ed..cf3d5b87 100644 --- a/internal/dataprovider/user.go +++ b/internal/dataprovider/user.go @@ -22,6 +22,7 @@ import ( "net" "os" "path" + "slices" "strconv" "strings" "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 func (u *User) HasPerm(permission, path string) bool { perms := u.GetPermissionsForPath(path) - if util.Contains(perms, PermAny) { + if slices.Contains(perms, PermAny) { 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 func (u *User) HasAnyPerm(permissions []string, path string) bool { perms := u.GetPermissionsForPath(path) - if util.Contains(perms, PermAny) { + if slices.Contains(perms, PermAny) { return true } for _, permission := range permissions { - if util.Contains(perms, permission) { + if slices.Contains(perms, permission) { 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 func (u *User) HasPerms(permissions []string, path string) bool { perms := u.GetPermissionsForPath(path) - if util.Contains(perms, PermAny) { + if slices.Contains(perms, PermAny) { return true } for _, permission := range permissions { - if !util.Contains(perms, permission) { + if !slices.Contains(perms, permission) { return false } } @@ -931,11 +932,11 @@ func (u *User) IsLoginMethodAllowed(loginMethod, protocol string) bool { if len(u.Filters.DeniedLoginMethods) == 0 { return true } - if util.Contains(u.Filters.DeniedLoginMethods, loginMethod) { + if slices.Contains(u.Filters.DeniedLoginMethods, loginMethod) { return false } if protocol == protocolSSH && loginMethod == LoginMethodPassword { - if util.Contains(u.Filters.DeniedLoginMethods, SSHLoginMethodPassword) { + if slices.Contains(u.Filters.DeniedLoginMethods, SSHLoginMethodPassword) { return false } } @@ -969,10 +970,10 @@ func (u *User) IsPartialAuth() bool { method == SSHLoginMethodPassword { continue } - if method == LoginMethodPassword && util.Contains(u.Filters.DeniedLoginMethods, SSHLoginMethodPassword) { + if method == LoginMethodPassword && slices.Contains(u.Filters.DeniedLoginMethods, SSHLoginMethodPassword) { continue } - if !util.Contains(SSHMultiStepsLoginMethods, method) { + if !slices.Contains(SSHMultiStepsLoginMethods, method) { return false } } @@ -986,7 +987,7 @@ func (u *User) GetAllowedLoginMethods() []string { if method == SSHLoginMethodPassword { continue } - if !util.Contains(u.Filters.DeniedLoginMethods, method) { + if !slices.Contains(u.Filters.DeniedLoginMethods, 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 func (u *User) CanManageMFA() bool { - if util.Contains(u.Filters.WebClient, sdk.WebClientMFADisabled) { + if slices.Contains(u.Filters.WebClient, sdk.WebClientMFADisabled) { return false } 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 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 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 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 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 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 // from the WebClient. Used in WebClient UI 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 // from the WebClient. Used in WebClient UI 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. @@ -1121,7 +1122,7 @@ func (u *User) CanUpdateProfile() bool { // 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 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 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. // The specified target is the directory where the new directory must be created 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 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. // The specified src and dest are the source and target directories for the rename. 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 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. // The specified target is the parent directory for the object to delete 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 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. // The specified src and dest are the source and target directories for the copy. 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 } if !u.HasPerm(PermListItems, src) { @@ -1217,7 +1218,7 @@ func (u *User) MustSetSecondFactor() bool { return true } for _, p := range u.Filters.TwoFactorAuthProtocols { - if !util.Contains(u.Filters.TOTPConfig.Protocols, p) { + if !slices.Contains(u.Filters.TOTPConfig.Protocols, p) { return true } } @@ -1228,11 +1229,11 @@ func (u *User) MustSetSecondFactor() bool { // MustSetSecondFactorForProtocol returns true if the user must set a second factor authentication // for the specified protocol 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 { return true } - if !util.Contains(u.Filters.TOTPConfig.Protocols, protocol) { + if !slices.Contains(u.Filters.TOTPConfig.Protocols, protocol) { return true } } diff --git a/internal/ftpd/server.go b/internal/ftpd/server.go index 8baf631c..7ce2b8ca 100644 --- a/internal/ftpd/server.go +++ b/internal/ftpd/server.go @@ -22,6 +22,7 @@ import ( "net" "os" "path/filepath" + "slices" ftpserver "github.com/fclairamb/ftpserverlib" "github.com/sftpgo/sdk/plugin/notifier" @@ -361,7 +362,7 @@ func (s *Server) validateUser(user dataprovider.User, cc ftpserver.ClientContext user.Username, 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) return nil, fmt.Errorf("protocol FTP is not allowed for user %q", user.Username) } diff --git a/internal/httpd/api_mfa.go b/internal/httpd/api_mfa.go index 3c4966cc..ccb44f7e 100644 --- a/internal/httpd/api_mfa.go +++ b/internal/httpd/api_mfa.go @@ -20,6 +20,7 @@ import ( "fmt" "io" "net/http" + "slices" "strconv" "strings" @@ -275,7 +276,7 @@ func saveUserTOTPConfig(username string, r *http.Request, recoveryCodes []datapr return util.NewValidationError("two-factor authentication must be enabled") } 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", strings.Join(userMerged.Filters.TwoFactorAuthProtocols, ", "))) } diff --git a/internal/httpd/api_shares.go b/internal/httpd/api_shares.go index 597cfa6f..612b14b3 100644 --- a/internal/httpd/api_shares.go +++ b/internal/httpd/api_shares.go @@ -22,6 +22,7 @@ import ( "net/url" "os" "path" + "slices" "strings" "time" @@ -107,7 +108,7 @@ func addShare(w http.ResponseWriter, r *http.Request) { share.Name = share.ShareID } 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", http.StatusForbidden) return @@ -155,7 +156,7 @@ func updateShare(w http.ResponseWriter, r *http.Request) { updatedShare.Password = share.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", http.StatusForbidden) return @@ -434,7 +435,7 @@ func (s *httpdServer) getShareClaims(r *http.Request, shareID string) (*jwtToken if tokenString == "" || invalidatedJWTTokens.Get(tokenString) { 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) return nil, errInvalidToken } @@ -486,7 +487,7 @@ func (s *httpdServer) checkPublicShare(w http.ResponseWriter, r *http.Request, v renderError(err, "", statusCode) return share, nil, err } - if !util.Contains(validScopes, share.Scope) { + if !slices.Contains(validScopes, share.Scope) { err := errors.New("invalid share scope") renderError(util.NewI18nError(err, util.I18nErrorShareScope), "", http.StatusForbidden) return share, nil, err @@ -543,7 +544,7 @@ func getUserForShare(share dataprovider.Share) (dataprovider.User, error) { if !user.CanManageShares() { 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( fmt.Errorf("sharing without a password was disabled: %w", os.ErrPermission), util.I18nError403Message, diff --git a/internal/httpd/api_utils.go b/internal/httpd/api_utils.go index f11094e1..3230695e 100644 --- a/internal/httpd/api_utils.go +++ b/internal/httpd/api_utils.go @@ -27,6 +27,7 @@ import ( "net/url" "os" "path" + "slices" "strconv" "strings" "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 { - 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) return util.NewI18nError( 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() { return false } - if util.Contains(user.Filters.DeniedProtocols, common.ProtocolHTTP) { + if slices.Contains(user.Filters.DeniedProtocols, common.ProtocolHTTP) { return false } if !user.IsLoginMethodAllowed(dataprovider.LoginMethodPassword, common.ProtocolHTTP) { diff --git a/internal/httpd/auth_utils.go b/internal/httpd/auth_utils.go index 08d74a8a..12d24a4f 100644 --- a/internal/httpd/auth_utils.go +++ b/internal/httpd/auth_utils.go @@ -18,6 +18,7 @@ import ( "errors" "fmt" "net/http" + "slices" "time" "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 { - if util.Contains(permissions, dataprovider.PermAdminAny) { + if slices.Contains(permissions, dataprovider.PermAdminAny) { return false } - if (util.Contains(c.Permissions, dataprovider.PermAdminManageAdmins) || - util.Contains(c.Permissions, dataprovider.PermAdminAny)) && - !util.Contains(permissions, dataprovider.PermAdminManageAdmins) && - !util.Contains(permissions, dataprovider.PermAdminAny) { + if (slices.Contains(c.Permissions, dataprovider.PermAdminManageAdmins) || + slices.Contains(c.Permissions, dataprovider.PermAdminAny)) && + !slices.Contains(permissions, dataprovider.PermAdminManageAdmins) && + !slices.Contains(permissions, dataprovider.PermAdminAny) { return true } return false } func (c *jwtTokenClaims) hasPerm(perm string) bool { - if util.Contains(c.Permissions, dataprovider.PermAdminAny) { + if slices.Contains(c.Permissions, dataprovider.PermAdminAny) { 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) { @@ -458,7 +459,7 @@ func verifyCSRFToken(r *http.Request, csrfTokenAuth *jwtauth.JWTAuth) error { 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") 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") 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) 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") 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 { if tokenValidationMode != tokenValidationNoIPMatch { - if !util.Contains(token.Audience(), ip) { + if !slices.Contains(token.Audience(), ip) { return errInvalidToken } } diff --git a/internal/httpd/httpd_test.go b/internal/httpd/httpd_test.go index 13d70e42..83f3242e 100644 --- a/internal/httpd/httpd_test.go +++ b/internal/httpd/httpd_test.go @@ -36,6 +36,7 @@ import ( "path/filepath" "regexp" "runtime" + "slices" "strconv" "strings" "sync" @@ -1340,7 +1341,7 @@ func TestGroupSettingsOverride(t *testing.T) { var folderNames []string if assert.Len(t, user.VirtualFolders, 4) { for _, f := range user.VirtualFolders { - if !util.Contains(folderNames, f.Name) { + if !slices.Contains(folderNames, f.Name) { folderNames = append(folderNames, f.Name) } switch f.Name { @@ -1348,7 +1349,7 @@ func TestGroupSettingsOverride(t *testing.T) { assert.Equal(t, mappedPath1, f.MappedPath) assert.Equal(t, 3, f.BaseVirtualFolder.FsConfig.OSConfig.ReadBufferSize) 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: assert.Equal(t, mappedPath2, f.MappedPath) assert.Equal(t, "/vdir3", f.VirtualPath) @@ -2103,16 +2104,16 @@ func TestActionRuleRelations(t *testing.T) { action1, _, err = httpdtest.GetEventActionByName(action1.Name, http.StatusOK) assert.NoError(t, err) 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) assert.NoError(t, err) 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) assert.NoError(t, err) assert.Len(t, action3.Rules, 2) - assert.True(t, util.Contains(action3.Rules, rule1.Name)) - assert.True(t, util.Contains(action3.Rules, rule2.Name)) + assert.True(t, slices.Contains(action3.Rules, rule1.Name)) + assert.True(t, slices.Contains(action3.Rules, rule2.Name)) // referenced actions cannot be removed _, err = httpdtest.RemoveEventAction(action1, http.StatusBadRequest) assert.NoError(t, err) @@ -2140,7 +2141,7 @@ func TestActionRuleRelations(t *testing.T) { action3, _, err = httpdtest.GetEventActionByName(action3.Name, http.StatusOK) assert.NoError(t, err) 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) assert.NoError(t, err) @@ -8912,7 +8913,7 @@ func TestBasicUserHandlingMock(t *testing.T) { assert.Equal(t, user.MaxSessions, updatedUser.MaxSessions) assert.Equal(t, user.UploadBandwidth, updatedUser.UploadBandwidth) 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) setBearerForReq(req, token) rr = executeRequest(req) @@ -12140,7 +12141,7 @@ func TestUserPermissionsMock(t *testing.T) { err = render.DecodeJSON(rr.Body, &updatedUser) assert.NoError(t, err) 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)) } else { 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.Greater(t, newUser.LastPasswordChange, int64(0)) 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 { - assert.True(t, util.Contains(val, dataprovider.PermListItems)) - assert.True(t, util.Contains(val, dataprovider.PermDownload)) + assert.True(t, slices.Contains(val, dataprovider.PermListItems)) + assert.True(t, slices.Contains(val, dataprovider.PermDownload)) } else { assert.Fail(t, "user permissions must contain /somedir", "actual: %v", newUser.Permissions) } @@ -21574,20 +21575,20 @@ func TestWebUserAddMock(t *testing.T) { case "/dir1": assert.Len(t, filter.DeniedPatterns, 1) assert.Len(t, filter.AllowedPatterns, 1) - assert.True(t, util.Contains(filter.AllowedPatterns, "*.png")) - assert.True(t, util.Contains(filter.DeniedPatterns, "*.zip")) + assert.True(t, slices.Contains(filter.AllowedPatterns, "*.png")) + assert.True(t, slices.Contains(filter.DeniedPatterns, "*.zip")) assert.Equal(t, sdk.DenyPolicyDefault, filter.DenyPolicy) case "/dir2": assert.Len(t, filter.DeniedPatterns, 1) assert.Len(t, filter.AllowedPatterns, 2) - assert.True(t, util.Contains(filter.AllowedPatterns, "*.jpg")) - assert.True(t, util.Contains(filter.AllowedPatterns, "*.png")) - assert.True(t, util.Contains(filter.DeniedPatterns, "*.mkv")) + assert.True(t, slices.Contains(filter.AllowedPatterns, "*.jpg")) + assert.True(t, slices.Contains(filter.AllowedPatterns, "*.png")) + assert.True(t, slices.Contains(filter.DeniedPatterns, "*.mkv")) assert.Equal(t, sdk.DenyPolicyHide, filter.DenyPolicy) case "/dir3": assert.Len(t, filter.DeniedPatterns, 1) 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) } } @@ -21828,16 +21829,16 @@ func TestWebUserUpdateMock(t *testing.T) { assert.Equal(t, 40, updateUser.Filters.PasswordStrength) assert.True(t, updateUser.Filters.RequirePasswordChange) if val, ok := updateUser.Permissions["/otherdir"]; ok { - assert.True(t, util.Contains(val, dataprovider.PermListItems)) - assert.True(t, util.Contains(val, dataprovider.PermUpload)) + assert.True(t, slices.Contains(val, dataprovider.PermListItems)) + assert.True(t, slices.Contains(val, dataprovider.PermUpload)) } else { 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, util.Contains(updateUser.Filters.DeniedIP, "10.0.0.2/32")) - assert.True(t, util.Contains(updateUser.Filters.DeniedLoginMethods, dataprovider.SSHLoginMethodKeyboardInteractive)) - assert.True(t, util.Contains(updateUser.Filters.DeniedProtocols, common.ProtocolFTP)) - assert.True(t, util.Contains(updateUser.Filters.FilePatterns[0].DeniedPatterns, "*.zip")) + assert.True(t, slices.Contains(updateUser.Filters.AllowedIP, "192.168.1.3/32")) + assert.True(t, slices.Contains(updateUser.Filters.DeniedIP, "10.0.0.2/32")) + assert.True(t, slices.Contains(updateUser.Filters.DeniedLoginMethods, dataprovider.SSHLoginMethodKeyboardInteractive)) + assert.True(t, slices.Contains(updateUser.Filters.DeniedProtocols, common.ProtocolFTP)) + assert.True(t, slices.Contains(updateUser.Filters.FilePatterns[0].DeniedPatterns, "*.zip")) assert.Len(t, updateUser.Filters.BandwidthLimits, 0) assert.Len(t, updateUser.Filters.TLSCerts, 1) req, err = http.NewRequest(http.MethodDelete, path.Join(userPath, user.Username), nil) diff --git a/internal/httpd/middleware.go b/internal/httpd/middleware.go index 79d09ecc..12d69736 100644 --- a/internal/httpd/middleware.go +++ b/internal/httpd/middleware.go @@ -20,6 +20,7 @@ import ( "io/fs" "net/http" "net/url" + "slices" "strings" "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 { 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) doRedirect("Your token audience is not valid", nil) return errInvalidToken @@ -113,7 +114,7 @@ func (s *httpdServer) validateJWTPartialToken(w http.ResponseWriter, r *http.Req notFoundFunc(w, r, nil) 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) notFoundFunc(w, r, nil) return errInvalidToken @@ -331,7 +332,7 @@ func (s *httpdServer) verifyCSRFHeader(next http.Handler) http.Handler { return } - if !util.Contains(token.Audience(), tokenAudienceCSRF) { + if !slices.Contains(token.Audience(), tokenAudienceCSRF) { logger.Debug(logSender, "", "error validating CSRF header token audience") sendAPIResponse(w, r, errors.New("the token is not valid"), "", http.StatusForbidden) 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 { - if audience == tokenAudienceWebAdmin && util.Contains(tokenAudience, tokenAudienceWebAdminPartial) { + if audience == tokenAudienceWebAdmin && slices.Contains(tokenAudience, tokenAudienceWebAdminPartial) { http.Redirect(w, r, webAdminTwoFactorPath, http.StatusFound) return errInvalidToken } - if audience == tokenAudienceWebClient && util.Contains(tokenAudience, tokenAudienceWebClientPartial) { + if audience == tokenAudienceWebClient && slices.Contains(tokenAudience, tokenAudienceWebClientPartial) { http.Redirect(w, r, webClientTwoFactorPath, http.StatusFound) return errInvalidToken } diff --git a/internal/httpd/oidc.go b/internal/httpd/oidc.go index bf4027b1..19af66b3 100644 --- a/internal/httpd/oidc.go +++ b/internal/httpd/oidc.go @@ -21,6 +21,7 @@ import ( "fmt" "net/http" "net/url" + "slices" "strings" "time" @@ -143,7 +144,7 @@ func (o *OIDC) initialize() error { if o.RedirectBaseURL == "" { 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) } if o.ClientSecretFile != "" { diff --git a/internal/httpd/server.go b/internal/httpd/server.go index a7405dcc..22de7e63 100644 --- a/internal/httpd/server.go +++ b/internal/httpd/server.go @@ -26,6 +26,7 @@ import ( "net/url" "path" "path/filepath" + "slices" "strings" "time" @@ -355,7 +356,7 @@ func (s *httpdServer) handleWebClientTwoFactorRecoveryPost(w http.ResponseWriter util.NewI18nError(dataprovider.ErrInvalidCredentials, util.I18nErrorInvalidCredentials)) 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( util.NewValidationError("two factory authentication is not enabled"), util.I18n2FADisabled)) return @@ -423,7 +424,7 @@ func (s *httpdServer) handleWebClientTwoFactorPost(w http.ResponseWriter, r *htt s.renderClientTwoFactorPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCredentials)) 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) s.renderClientTwoFactorPage(w, r, util.NewI18nError(common.ErrInternalFailure, util.I18n2FADisabled)) return @@ -743,7 +744,7 @@ func (s *httpdServer) loginUser( } 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 { audience = tokenAudienceWebClientPartial } @@ -863,7 +864,7 @@ func (s *httpdServer) getUserToken(w http.ResponseWriter, r *http.Request) { 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) if passcode == "" { 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 { return } - if util.Contains(token.Audience(), tokenAudienceWebClient) { + if slices.Contains(token.Audience(), tokenAudienceWebClient) { s.refreshClientToken(w, r, &tokenClaims) } else { s.refreshAdminToken(w, r, &tokenClaims) diff --git a/internal/httpd/webadmin.go b/internal/httpd/webadmin.go index 4d573ecd..7b7933ca 100644 --- a/internal/httpd/webadmin.go +++ b/internal/httpd/webadmin.go @@ -25,6 +25,7 @@ import ( "net/url" "os" "path/filepath" + "slices" "sort" "strconv" "strings" @@ -1488,13 +1489,13 @@ func getFiltersFromUserPostFields(r *http.Request) (sdk.BaseUserFilters, error) filters.PasswordStrength = passwordStrength filters.AccessTime = getAccessTimeRestrictionsFromPostFields(r) hooks := r.Form["hooks"] - if util.Contains(hooks, "external_auth_disabled") { + if slices.Contains(hooks, "external_auth_disabled") { filters.Hooks.ExternalAuthDisabled = true } - if util.Contains(hooks, "pre_login_disabled") { + if slices.Contains(hooks, "pre_login_disabled") { filters.Hooks.PreLoginDisabled = true } - if util.Contains(hooks, "check_password_disabled") { + if slices.Contains(hooks, "check_password_disabled") { filters.Hooks.CheckPasswordDisabled = true } filters.IsAnonymous = r.Form.Get("is_anonymous") != "" @@ -2215,7 +2216,7 @@ func getFoldersRetentionFromPostFields(r *http.Request) ([]dataprovider.FolderRe res = append(res, dataprovider.FolderRetention{ Path: p, 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, Options: dataprovider.EventActionOptions{ - IsFailureAction: util.Contains(options, "1"), - StopOnFailure: util.Contains(options, "2"), - ExecuteSync: util.Contains(options, "3"), + IsFailureAction: slices.Contains(options, "1"), + StopOnFailure: slices.Contains(options, "2"), + ExecuteSync: slices.Contains(options, "3"), }, }) } diff --git a/internal/httpd/webclient.go b/internal/httpd/webclient.go index c62831c9..6f6a611e 100644 --- a/internal/httpd/webclient.go +++ b/internal/httpd/webclient.go @@ -27,6 +27,7 @@ import ( "os" "path" "path/filepath" + "slices" "strconv" "strings" "time" @@ -1463,7 +1464,7 @@ func (s *httpdServer) handleClientAddSharePost(w http.ResponseWriter, r *http.Re share.LastUseAt = 0 share.Username = claims.Username if share.Password == "" { - if util.Contains(claims.Permissions, sdk.WebClientShareNoPasswordDisabled) { + if slices.Contains(claims.Permissions, sdk.WebClientShareNoPasswordDisabled) { s.renderAddUpdateSharePage(w, r, share, util.NewI18nError(util.NewValidationError("You are not allowed to share files/folders without password"), util.I18nErrorShareNoPwd), true) @@ -1532,7 +1533,7 @@ func (s *httpdServer) handleClientUpdateSharePost(w http.ResponseWriter, r *http updatedShare.Password = share.Password } if updatedShare.Password == "" { - if util.Contains(claims.Permissions, sdk.WebClientShareNoPasswordDisabled) { + if slices.Contains(claims.Permissions, sdk.WebClientShareNoPasswordDisabled) { s.renderAddUpdateSharePage(w, r, updatedShare, util.NewI18nError(util.NewValidationError("You are not allowed to share files/folders without password"), util.I18nErrorShareNoPwd), false) @@ -2015,7 +2016,7 @@ func doCheckExist(w http.ResponseWriter, r *http.Request, connection *Connection } existing := make([]map[string]any, 0) for _, info := range contents { - if util.Contains(filesList.Files, info.Name()) { + if slices.Contains(filesList.Files, info.Name()) { res := make(map[string]any) res["name"] = info.Name() if info.IsDir() { diff --git a/internal/httpdtest/httpdtest.go b/internal/httpdtest/httpdtest.go index 382e2ca0..c78c97b2 100644 --- a/internal/httpdtest/httpdtest.go +++ b/internal/httpdtest/httpdtest.go @@ -25,6 +25,7 @@ import ( "net/http" "net/url" "path" + "slices" "strconv" "strings" @@ -36,7 +37,6 @@ import ( "github.com/drakkan/sftpgo/v2/internal/httpclient" "github.com/drakkan/sftpgo/v2/internal/httpd" "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/vfs" ) @@ -1679,7 +1679,7 @@ func checkEventConditionOptions(expected, actual dataprovider.ConditionOptions) return errors.New("condition protocols mismatch") } for _, v := range expected.Protocols { - if !util.Contains(actual.Protocols, v) { + if !slices.Contains(actual.Protocols, v) { return errors.New("condition protocols content mismatch") } } @@ -1687,7 +1687,7 @@ func checkEventConditionOptions(expected, actual dataprovider.ConditionOptions) return errors.New("condition provider objects mismatch") } 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") } } @@ -1705,7 +1705,7 @@ func checkEventConditions(expected, actual dataprovider.EventConditions) error { return errors.New("fs events mismatch") } for _, v := range expected.FsEvents { - if !util.Contains(actual.FsEvents, v) { + if !slices.Contains(actual.FsEvents, v) { return errors.New("fs events content mismatch") } } @@ -1713,7 +1713,7 @@ func checkEventConditions(expected, actual dataprovider.EventConditions) error { return errors.New("provider events mismatch") } for _, v := range expected.ProviderEvents { - if !util.Contains(actual.ProviderEvents, v) { + if !slices.Contains(actual.ProviderEvents, v) { return errors.New("provider events content mismatch") } } @@ -1948,7 +1948,7 @@ func checkAdmin(expected, actual *dataprovider.Admin) error { return errors.New("permissions mismatch") } for _, p := range expected.Permissions { - if !util.Contains(actual.Permissions, p) { + if !slices.Contains(actual.Permissions, p) { return errors.New("permissions content mismatch") } } @@ -1966,7 +1966,7 @@ func compareAdminFilters(expected, actual dataprovider.AdminFilters) error { return errors.New("allow list mismatch") } for _, v := range expected.AllowList { - if !util.Contains(actual.AllowList, v) { + if !slices.Contains(actual.AllowList, v) { 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 { if actualPerms, ok := actual[dir]; ok { for _, v := range actualPerms { - if !util.Contains(perms, v) { + if !slices.Contains(perms, v) { 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") } 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") } } @@ -2401,27 +2401,27 @@ func checkEncryptedSecret(expected, actual *kms.Secret) error { func compareUserFilterSubStructs(expected sdk.BaseUserFilters, actual sdk.BaseUserFilters) error { for _, IPMask := range expected.AllowedIP { - if !util.Contains(actual.AllowedIP, IPMask) { + if !slices.Contains(actual.AllowedIP, IPMask) { return errors.New("allowed IP contents mismatch") } } for _, IPMask := range expected.DeniedIP { - if !util.Contains(actual.DeniedIP, IPMask) { + if !slices.Contains(actual.DeniedIP, IPMask) { return errors.New("denied IP contents mismatch") } } 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") } } for _, protocol := range expected.DeniedProtocols { - if !util.Contains(actual.DeniedProtocols, protocol) { + if !slices.Contains(actual.DeniedProtocols, protocol) { return errors.New("denied protocols contents mismatch") } } 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") } } @@ -2430,7 +2430,7 @@ func compareUserFilterSubStructs(expected sdk.BaseUserFilters, actual sdk.BaseUs return errors.New("TLS certs mismatch") } for _, cert := range expected.TLSCerts { - if !util.Contains(actual.TLSCerts, cert) { + if !slices.Contains(actual.TLSCerts, cert) { return errors.New("TLS certs content mismatch") } } @@ -2527,7 +2527,7 @@ func checkFilterMatch(expected []string, actual []string) bool { return false } for _, e := range expected { - if !util.Contains(actual, strings.ToLower(e)) { + if !slices.Contains(actual, strings.ToLower(e)) { return false } } @@ -2570,7 +2570,7 @@ func compareUserBandwidthLimitFilters(expected sdk.BaseUserFilters, actual sdk.B return errors.New("bandwidth filters sources mismatch") } 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") } } @@ -2680,7 +2680,7 @@ func compareEventActionEmailConfigFields(expected, actual dataprovider.EventActi return errors.New("email recipients mismatch") } for _, v := range expected.Recipients { - if !util.Contains(actual.Recipients, v) { + if !slices.Contains(actual.Recipients, v) { return errors.New("email recipients content mismatch") } } @@ -2688,7 +2688,7 @@ func compareEventActionEmailConfigFields(expected, actual dataprovider.EventActi return errors.New("email bcc mismatch") } for _, v := range expected.Bcc { - if !util.Contains(actual.Bcc, v) { + if !slices.Contains(actual.Bcc, v) { return errors.New("email bcc content mismatch") } } @@ -2705,7 +2705,7 @@ func compareEventActionEmailConfigFields(expected, actual dataprovider.EventActi return errors.New("email attachments mismatch") } for _, v := range expected.Attachments { - if !util.Contains(actual.Attachments, v) { + if !slices.Contains(actual.Attachments, v) { return errors.New("email attachments content mismatch") } } @@ -2720,7 +2720,7 @@ func compareEventActionFsCompressFields(expected, actual dataprovider.EventActio return errors.New("fs compress paths mismatch") } 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") } } @@ -2741,7 +2741,7 @@ func compareEventActionFsConfigFields(expected, actual dataprovider.EventActionF return errors.New("fs deletes mismatch") } for _, v := range expected.Deletes { - if !util.Contains(actual.Deletes, v) { + if !slices.Contains(actual.Deletes, v) { return errors.New("fs deletes content mismatch") } } @@ -2749,7 +2749,7 @@ func compareEventActionFsConfigFields(expected, actual dataprovider.EventActionF return errors.New("fs mkdirs mismatch") } for _, v := range expected.MkDirs { - if !util.Contains(actual.MkDirs, v) { + if !slices.Contains(actual.MkDirs, v) { return errors.New("fs mkdir content mismatch") } } @@ -2757,7 +2757,7 @@ func compareEventActionFsConfigFields(expected, actual dataprovider.EventActionF return errors.New("fs exist mismatch") } for _, v := range expected.Exist { - if !util.Contains(actual.Exist, v) { + if !slices.Contains(actual.Exist, v) { return errors.New("fs exist content mismatch") } } @@ -2788,7 +2788,7 @@ func compareEventActionCmdConfigFields(expected, actual dataprovider.EventAction return errors.New("cmd args mismatch") } for _, v := range expected.Args { - if !util.Contains(actual.Args, v) { + if !slices.Contains(actual.Args, v) { return errors.New("cmd args content mismatch") } } diff --git a/internal/plugin/kms.go b/internal/plugin/kms.go index 964e0cdb..6ab30e1f 100644 --- a/internal/plugin/kms.go +++ b/internal/plugin/kms.go @@ -17,6 +17,7 @@ package plugin import ( "fmt" "path/filepath" + "slices" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-plugin" @@ -25,7 +26,6 @@ import ( "github.com/drakkan/sftpgo/v2/internal/kms" "github.com/drakkan/sftpgo/v2/internal/logger" - "github.com/drakkan/sftpgo/v2/internal/util" ) var ( @@ -41,10 +41,10 @@ type KMSConfig struct { } 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) } - if !util.Contains(validKMSEncryptedStatuses, c.EncryptedStatus) { + if !slices.Contains(validKMSEncryptedStatuses, c.EncryptedStatus) { return fmt.Errorf("invalid kms encrypted status: %v", c.EncryptedStatus) } return nil diff --git a/internal/plugin/notifier.go b/internal/plugin/notifier.go index 0e9ac9ec..c1d58a6b 100644 --- a/internal/plugin/notifier.go +++ b/internal/plugin/notifier.go @@ -16,6 +16,7 @@ package plugin import ( "fmt" + "slices" "sync" "time" @@ -24,7 +25,6 @@ import ( "github.com/sftpgo/sdk/plugin/notifier" "github.com/drakkan/sftpgo/v2/internal/logger" - "github.com/drakkan/sftpgo/v2/internal/util" ) // 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) { - if !util.Contains(p.config.NotifierOptions.FsEvents, event.Action) { + if !slices.Contains(p.config.NotifierOptions.FsEvents, event.Action) { return } @@ -233,8 +233,8 @@ func (p *notifierPlugin) notifyFsAction(event *notifier.FsEvent) { } func (p *notifierPlugin) notifyProviderAction(event *notifier.ProviderEvent, object Renderer) { - if !util.Contains(p.config.NotifierOptions.ProviderEvents, event.Action) || - !util.Contains(p.config.NotifierOptions.ProviderObjects, event.ObjectType) { + if !slices.Contains(p.config.NotifierOptions.ProviderEvents, event.Action) || + !slices.Contains(p.config.NotifierOptions.ProviderObjects, event.ObjectType) { return } diff --git a/internal/plugin/plugin.go b/internal/plugin/plugin.go index b3f65b45..eb772033 100644 --- a/internal/plugin/plugin.go +++ b/internal/plugin/plugin.go @@ -24,6 +24,7 @@ import ( "os" "os/exec" "path/filepath" + "slices" "strings" "sync" "sync/atomic" @@ -336,7 +337,7 @@ func (m *Manager) NotifyLogEvent(event notifier.LogEventType, protocol, username var e *notifier.LogEvent 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 { message := "" if err != nil { diff --git a/internal/service/service_portable.go b/internal/service/service_portable.go index 06b05f7a..3a9c86bf 100644 --- a/internal/service/service_portable.go +++ b/internal/service/service_portable.go @@ -20,6 +20,7 @@ package service import ( "fmt" "math/rand" + "slices" "strings" "github.com/sftpgo/sdk" @@ -211,7 +212,7 @@ func configurePortableSFTPService(port int, enabledSSHCommands []string) { } else { sftpdConf.Bindings[0].Port = 0 } - if util.Contains(enabledSSHCommands, "*") { + if slices.Contains(enabledSSHCommands, "*") { sftpdConf.EnabledSSHCommands = sftpd.GetSupportedSSHCommands() } else { sftpdConf.EnabledSSHCommands = enabledSSHCommands diff --git a/internal/sftpd/internal_test.go b/internal/sftpd/internal_test.go index bc96b68a..3ddfe400 100644 --- a/internal/sftpd/internal_test.go +++ b/internal/sftpd/internal_test.go @@ -24,6 +24,7 @@ import ( "os" "path/filepath" "runtime" + "slices" "testing" "time" @@ -418,7 +419,7 @@ func TestSupportedSSHCommands(t *testing.T) { assert.Equal(t, len(supportedSSHCommands), len(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() 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") permissions["/"] = []string{dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermCreateDirs, @@ -859,7 +860,7 @@ func TestRsyncOptions(t *testing.T) { } cmd, err = sshCmd.getSystemCommand() 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") sshCmd.connection.User.VirtualFolders = append(sshCmd.connection.User.VirtualFolders, vfs.VirtualFolder{ diff --git a/internal/sftpd/server.go b/internal/sftpd/server.go index d4b255f6..1e347421 100644 --- a/internal/sftpd/server.go +++ b/internal/sftpd/server.go @@ -26,6 +26,7 @@ import ( "os" "path/filepath" "runtime/debug" + "slices" "strings" "sync" "time" @@ -263,13 +264,13 @@ func (c *Configuration) getServerConfig() *ssh.ServerConfig { func (c *Configuration) updateSupportedAuthentications() { serviceStatus.Authentications = util.RemoveDuplicates(serviceStatus.Authentications, false) - if util.Contains(serviceStatus.Authentications, dataprovider.LoginMethodPassword) && - util.Contains(serviceStatus.Authentications, dataprovider.SSHLoginMethodPublicKey) { + if slices.Contains(serviceStatus.Authentications, dataprovider.LoginMethodPassword) && + slices.Contains(serviceStatus.Authentications, dataprovider.SSHLoginMethodPublicKey) { serviceStatus.Authentications = append(serviceStatus.Authentications, dataprovider.SSHLoginMethodKeyAndPassword) } - if util.Contains(serviceStatus.Authentications, dataprovider.SSHLoginMethodKeyboardInteractive) && - util.Contains(serviceStatus.Authentications, dataprovider.SSHLoginMethodPublicKey) { + if slices.Contains(serviceStatus.Authentications, dataprovider.SSHLoginMethodKeyboardInteractive) && + slices.Contains(serviceStatus.Authentications, dataprovider.SSHLoginMethodPublicKey) { 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) } for _, hostKeyAlgo := range c.HostKeyAlgorithms { - if !util.Contains(supportedHostKeyAlgos, hostKeyAlgo) { + if !slices.Contains(supportedHostKeyAlgos, 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 { c.PublicKeyAlgorithms = util.RemoveDuplicates(c.PublicKeyAlgorithms, true) 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) } } @@ -472,7 +473,7 @@ func (c *Configuration) configureSecurityOptions(serverConfig *ssh.ServerConfig) if kex == keyExchangeCurve25519SHA256LibSSH { continue } - if !util.Contains(supportedKexAlgos, kex) { + if !slices.Contains(supportedKexAlgos, 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 { c.Ciphers = util.RemoveDuplicates(c.Ciphers, true) for _, cipher := range c.Ciphers { - if !util.Contains(supportedCiphers, cipher) { + if !slices.Contains(supportedCiphers, cipher) { return fmt.Errorf("unsupported cipher %q", cipher) } } @@ -499,7 +500,7 @@ func (c *Configuration) configureSecurityOptions(serverConfig *ssh.ServerConfig) if len(c.MACs) > 0 { c.MACs = util.RemoveDuplicates(c.MACs, true) for _, mac := range c.MACs { - if !util.Contains(supportedMACs, mac) { + if !slices.Contains(supportedMACs, 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) 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) 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() { - if util.Contains(c.EnabledSSHCommands, "*") { + if slices.Contains(c.EnabledSSHCommands, "*") { c.EnabledSSHCommands = GetSupportedSSHCommands() return } sshCommands := []string{} for _, command := range c.EnabledSSHCommands { command = strings.TrimSpace(command) - if util.Contains(supportedSSHCommands, command) { + if slices.Contains(supportedSSHCommands, command) { sshCommands = append(sshCommands, command) } else { 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 { var algos []string for _, algo := range algorithmsForKeyFormat(keyFormat) { - if util.Contains(c.HostKeyAlgorithms, algo) { + if slices.Contains(c.HostKeyAlgorithms, algo) { algos = append(algos, algo) } } @@ -986,7 +987,7 @@ func (c *Configuration) checkAndLoadHostKeys(configDir string, serverConfig *ssh var algos []string for _, algo := range algorithmsForKeyFormat(signer.PublicKey().Type()) { if underlyingAlgo, ok := certKeyAlgoNames[algo]; ok { - if util.Contains(mas.Algorithms(), underlyingAlgo) { + if slices.Contains(mas.Algorithms(), underlyingAlgo) { algos = append(algos, algo) } } @@ -1098,12 +1099,12 @@ func (c *Configuration) initializeCertChecker(configDir string) error { func (c *Configuration) getPartialSuccessError(nextAuthMethods []string) error { 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) { 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) { return c.validateKeyboardInteractiveCredentials(conn, client, dataprovider.SSHLoginMethodKeyAndKeyboardInt, true) } diff --git a/internal/sftpd/sftpd_test.go b/internal/sftpd/sftpd_test.go index 12b154fd..fee015e0 100644 --- a/internal/sftpd/sftpd_test.go +++ b/internal/sftpd/sftpd_test.go @@ -38,6 +38,7 @@ import ( "path" "path/filepath" "runtime" + "slices" "strconv" "strings" "sync" @@ -8639,8 +8640,8 @@ func TestUserAllowedLoginMethods(t *testing.T) { allowedMethods = user.GetAllowedLoginMethods() assert.Equal(t, 4, len(allowedMethods)) - assert.True(t, util.Contains(allowedMethods, dataprovider.SSHLoginMethodKeyAndKeyboardInt)) - assert.True(t, util.Contains(allowedMethods, dataprovider.SSHLoginMethodKeyAndPassword)) + assert.True(t, slices.Contains(allowedMethods, dataprovider.SSHLoginMethodKeyAndKeyboardInt)) + assert.True(t, slices.Contains(allowedMethods, dataprovider.SSHLoginMethodKeyAndPassword)) } func TestUserPartialAuth(t *testing.T) { diff --git a/internal/sftpd/ssh_cmd.go b/internal/sftpd/ssh_cmd.go index 7d4db85c..fa782f0e 100644 --- a/internal/sftpd/ssh_cmd.go +++ b/internal/sftpd/ssh_cmd.go @@ -27,6 +27,7 @@ import ( "os/exec" "path" "runtime/debug" + "slices" "strings" "sync" "time" @@ -91,7 +92,7 @@ func processSSHCommand(payload []byte, connection *Connection, enabledSSHCommand name, args, err := parseCommandPayload(msg.Command) 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) - if err == nil && util.Contains(enabledSSHCommands, name) { + if err == nil && slices.Contains(enabledSSHCommands, name) { connection.command = msg.Command if name == scpCmdName && len(args) >= 2 { connection.SetProtocol(common.ProtocolSCP) @@ -139,9 +140,9 @@ func (c *sshCommand) handle() (err error) { defer common.Connections.Remove(c.connection.GetID()) c.connection.UpdateLastActivity() - if util.Contains(sshHashCommands, c.command) { + if slices.Contains(sshHashCommands, c.command) { return c.handleHashCommands() - } else if util.Contains(systemCommands, c.command) { + } else if slices.Contains(systemCommands, c.command) { command, err := c.getSystemCommand() if err != nil { 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 // already set. This should make symlinks unusable (but manually recoverable) 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...) } } else { - if !util.Contains(args, "--munge-links") { + if !slices.Contains(args, "--munge-links") { args = append([]string{"--munge-links"}, args...) } } diff --git a/internal/smtp/oauth2.go b/internal/smtp/oauth2.go index 5fea89f5..87997947 100644 --- a/internal/smtp/oauth2.go +++ b/internal/smtp/oauth2.go @@ -19,6 +19,7 @@ import ( "context" "errors" "fmt" + "slices" "sync" "time" @@ -27,7 +28,6 @@ import ( "golang.org/x/oauth2/microsoft" "github.com/drakkan/sftpgo/v2/internal/logger" - "github.com/drakkan/sftpgo/v2/internal/util" ) // Supported OAuth2 providers @@ -56,7 +56,7 @@ type OAuth2Config struct { // Validate validates and initializes the configuration 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) } if c.ClientID == "" { diff --git a/internal/util/util.go b/internal/util/util.go index 0274d97d..03e68914 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -128,16 +128,6 @@ var bytesSizeTable = map[string]uint64{ "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 // returns the modified slice func Remove(elems []string, val string) []string { diff --git a/internal/vfs/osfs.go b/internal/vfs/osfs.go index affc44f4..73c552a9 100644 --- a/internal/vfs/osfs.go +++ b/internal/vfs/osfs.go @@ -24,6 +24,7 @@ import ( "os" "path" "path/filepath" + "slices" "strings" "time" @@ -34,7 +35,6 @@ import ( "github.com/sftpgo/sdk" "github.com/drakkan/sftpgo/v2/internal/logger" - "github.com/drakkan/sftpgo/v2/internal/util" ) const ( @@ -475,7 +475,7 @@ func (fs *OsFs) findNonexistentDirs(filePath string) ([]string, error) { for fs.IsNotExist(err) { results = append(results, parent) parent = filepath.Dir(parent) - if util.Contains(results, parent) { + if slices.Contains(results, parent) { break } _, err = os.Stat(parent) diff --git a/internal/vfs/s3fs.go b/internal/vfs/s3fs.go index b74d2877..e50b6f1e 100644 --- a/internal/vfs/s3fs.go +++ b/internal/vfs/s3fs.go @@ -30,6 +30,7 @@ import ( "os" "path" "path/filepath" + "slices" "sort" "strings" "sync" @@ -161,7 +162,7 @@ func (fs *S3Fs) Stat(name string) (os.FileInfo, error) { if err == nil { // 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". - isDir := util.Contains(s3DirMimeTypes, util.GetStringFromPointer(obj.ContentType)) + isDir := slices.Contains(s3DirMimeTypes, util.GetStringFromPointer(obj.ContentType)) if util.GetIntFromPointer(obj.ContentLength) == 0 && !isDir { _, err = fs.headObject(name + "/") isDir = err == nil diff --git a/internal/vfs/sftpfs.go b/internal/vfs/sftpfs.go index 445327bc..0a2f4214 100644 --- a/internal/vfs/sftpfs.go +++ b/internal/vfs/sftpfs.go @@ -28,6 +28,7 @@ import ( "os" "path" "path/filepath" + "slices" "strconv" "strings" "sync" @@ -125,7 +126,7 @@ func (c *SFTPFsConfig) isEqual(other SFTPFsConfig) bool { return false } for _, fp := range c.Fingerprints { - if !util.Contains(other.Fingerprints, fp) { + if !slices.Contains(other.Fingerprints, fp) { return false } } @@ -954,12 +955,12 @@ func (c *sftpConnection) openConnNoLock() error { User: c.config.Username, HostKeyCallback: func(_ string, _ net.Addr, key ssh.PublicKey) error { fp := ssh.FingerprintSHA256(key) - if util.Contains(sftpFingerprints, fp) { + if slices.Contains(sftpFingerprints, fp) { if allowSelfConnections == 0 { logger.Log(logger.LevelError, c.logSender, "", "SFTP self connections not allowed") 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, "", "SFTP loop or nested local SFTP folders detected, username %q, forbidden usernames: %+v", c.config.Username, c.config.forbiddenSelfUsernames) diff --git a/internal/vfs/vfs.go b/internal/vfs/vfs.go index b8edcb34..a3f1290b 100644 --- a/internal/vfs/vfs.go +++ b/internal/vfs/vfs.go @@ -24,6 +24,7 @@ import ( "path" "path/filepath" "runtime" + "slices" "strconv" "strings" "sync" @@ -764,7 +765,7 @@ func (c *AzBlobFsConfig) validate() error { if err := c.checkPartSizeAndConcurrency(); err != nil { 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 nil diff --git a/internal/webdavd/file.go b/internal/webdavd/file.go index 345c2714..1f68dd65 100644 --- a/internal/webdavd/file.go +++ b/internal/webdavd/file.go @@ -23,6 +23,7 @@ import ( "net/http" "os" "path" + "slices" "sync/atomic" "time" @@ -447,7 +448,7 @@ func (f *webDavFile) Patch(patches []webdav.Proppatch) ([]webdav.Propstat, error pstat := webdav.Propstat{} for _, p := range patch.Props { 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)) if err != nil { f.Connection.Log(logger.LevelWarn, "unsupported last modification time: %q, err: %v", diff --git a/internal/webdavd/server.go b/internal/webdavd/server.go index aad07040..a1b9248e 100644 --- a/internal/webdavd/server.go +++ b/internal/webdavd/server.go @@ -26,6 +26,7 @@ import ( "path" "path/filepath" "runtime/debug" + "slices" "strings" "time" @@ -346,7 +347,7 @@ func (s *webDavServer) validateUser(user *dataprovider.User, r *http.Request, lo user.Username, 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) return connID, fmt.Errorf("protocol DAV is not allowed for user %q", user.Username) }