فهرست منبع

fix a potential race condition for pre-login and ext auth
hooks

doing something like this:

err = provider.updateUser(u)
...
return provider.userExists(username)

could be racy if another update happen before

provider.userExists(username)

also pass a pointer to updateUser so if the user is modified inside
"validateUser" we can just return the modified user without do a new
query

Nicola Murino 4 سال پیش
والد
کامیت
daac90c4e1

+ 2 - 2
cmd/startsubsys.go

@@ -96,7 +96,7 @@ Command-line flags should be specified in the Subsystem declaration.
 				if user.HomeDir != filepath.Clean(homedir) && !preserveHomeDir {
 					// update the user
 					user.HomeDir = filepath.Clean(homedir)
-					err = dataprovider.UpdateUser(user)
+					err = dataprovider.UpdateUser(&user)
 					if err != nil {
 						logger.Error(logSender, connectionID, "unable to update user %#v: %v", username, err)
 						os.Exit(1)
@@ -113,7 +113,7 @@ Command-line flags should be specified in the Subsystem declaration.
 				user.Password = connectionID
 				user.Permissions = make(map[string][]string)
 				user.Permissions["/"] = []string{dataprovider.PermAny}
-				err = dataprovider.AddUser(user)
+				err = dataprovider.AddUser(&user)
 				if err != nil {
 					logger.Error(logSender, connectionID, "unable to add user %#v: %v", username, err)
 					os.Exit(1)

+ 2 - 2
common/common.go

@@ -364,7 +364,7 @@ func (c *Configuration) GetProxyListener(listener net.Listener) (*proxyproto.Lis
 
 // ExecutePostConnectHook executes the post connect hook if defined
 func (c *Configuration) ExecutePostConnectHook(ipAddr, protocol string) error {
-	if len(c.PostConnectHook) == 0 {
+	if c.PostConnectHook == "" {
 		return nil
 	}
 	if strings.HasPrefix(c.PostConnectHook, "http") {
@@ -594,7 +594,7 @@ func (conns *ActiveConnections) checkIdles() {
 
 	for _, c := range conns.connections {
 		idleTime := time.Since(c.GetLastActivity())
-		isUnauthenticatedFTPUser := (c.GetProtocol() == ProtocolFTP && len(c.GetUsername()) == 0)
+		isUnauthenticatedFTPUser := (c.GetProtocol() == ProtocolFTP && c.GetUsername() == "")
 
 		if idleTime > Config.idleTimeoutAsDuration || (isUnauthenticatedFTPUser && idleTime > Config.idleLoginTimeout) {
 			defer func(conn ActiveConnection, isFTPNoAuth bool) {

+ 8 - 8
common/connection_test.go

@@ -1031,7 +1031,7 @@ func TestHasSpace(t *testing.T) {
 
 	user.VirtualFolders[0].QuotaFiles = 0
 	user.VirtualFolders[0].QuotaSize = 0
-	err = dataprovider.AddUser(user)
+	err = dataprovider.AddUser(&user)
 	assert.NoError(t, err)
 	user, err = dataprovider.UserExists(user.Username)
 	assert.NoError(t, err)
@@ -1041,7 +1041,7 @@ func TestHasSpace(t *testing.T) {
 
 	user.VirtualFolders[0].QuotaFiles = 10
 	user.VirtualFolders[0].QuotaSize = 1048576
-	err = dataprovider.UpdateUser(user)
+	err = dataprovider.UpdateUser(&user)
 	assert.NoError(t, err)
 	c.User = user
 	quotaResult = c.HasSpace(true, "/vdir/file1")
@@ -1057,10 +1057,10 @@ func TestHasSpace(t *testing.T) {
 	quotaResult = c.HasSpace(true, "/vdir/file1")
 	assert.False(t, quotaResult.HasSpace)
 
-	err = dataprovider.DeleteUser(user)
+	err = dataprovider.DeleteUser(&user)
 	assert.NoError(t, err)
 
-	err = dataprovider.DeleteFolder(folder)
+	err = dataprovider.DeleteFolder(&folder)
 	assert.NoError(t, err)
 }
 
@@ -1091,7 +1091,7 @@ func TestUpdateQuotaMoveVFolders(t *testing.T) {
 		QuotaFiles:  -1,
 		QuotaSize:   -1,
 	})
-	err := dataprovider.AddUser(user)
+	err := dataprovider.AddUser(&user)
 	assert.NoError(t, err)
 	user, err = dataprovider.UserExists(user.Username)
 	assert.NoError(t, err)
@@ -1148,11 +1148,11 @@ func TestUpdateQuotaMoveVFolders(t *testing.T) {
 	assert.Equal(t, 1, user.UsedQuotaFiles)
 	assert.Equal(t, int64(100), user.UsedQuotaSize)
 
-	err = dataprovider.DeleteUser(user)
+	err = dataprovider.DeleteUser(&user)
 	assert.NoError(t, err)
-	err = dataprovider.DeleteFolder(folder1)
+	err = dataprovider.DeleteFolder(&folder1)
 	assert.NoError(t, err)
-	err = dataprovider.DeleteFolder(folder2)
+	err = dataprovider.DeleteFolder(&folder2)
 	assert.NoError(t, err)
 }
 

+ 4 - 8
common/tlsutils.go

@@ -171,19 +171,15 @@ func (m *CertManager) LoadRootCAs() error {
 	return nil
 }
 
-// SetCACertificates sets the root CA authorities file paths
+// SetCACertificates sets the root CA authorities file paths.
+// This should not be changed at runtime
 func (m *CertManager) SetCACertificates(caCertificates []string) {
-	m.Lock()
-	defer m.Unlock()
-
 	m.caCertificates = caCertificates
 }
 
-// SetCARevocationLists sets the CA revocation lists file paths
+// SetCARevocationLists sets the CA revocation lists file paths.
+// This should not be changed at runtime
 func (m *CertManager) SetCARevocationLists(caRevocationLists []string) {
-	m.Lock()
-	defer m.Unlock()
-
 	m.caRevocationLists = caRevocationLists
 }
 

+ 17 - 16
dataprovider/bolt.go

@@ -101,7 +101,7 @@ func (p BoltProvider) checkAvailability() error {
 
 func (p BoltProvider) validateUserAndPass(username, password, ip, protocol string) (User, error) {
 	var user User
-	if len(password) == 0 {
+	if password == "" {
 		return user, errors.New("Credentials cannot be null or empty")
 	}
 	user, err := p.userExists(username)
@@ -246,8 +246,8 @@ func (p BoltProvider) userExists(username string) (User, error) {
 	return user, err
 }
 
-func (p BoltProvider) addUser(user User) error {
-	err := validateUser(&user)
+func (p BoltProvider) addUser(user *User) error {
+	err := validateUser(user)
 	if err != nil {
 		return err
 	}
@@ -291,8 +291,8 @@ func (p BoltProvider) addUser(user User) error {
 	})
 }
 
-func (p BoltProvider) updateUser(user User) error {
-	err := validateUser(&user)
+func (p BoltProvider) updateUser(user *User) error {
+	err := validateUser(user)
 	if err != nil {
 		return err
 	}
@@ -315,7 +315,7 @@ func (p BoltProvider) updateUser(user User) error {
 			return err
 		}
 		for _, folder := range oldUser.VirtualFolders {
-			err = removeUserFromFolderMapping(folder, oldUser, folderBucket)
+			err = removeUserFromFolderMapping(folder, &oldUser, folderBucket)
 			if err != nil {
 				return err
 			}
@@ -339,7 +339,7 @@ func (p BoltProvider) updateUser(user User) error {
 	})
 }
 
-func (p BoltProvider) deleteUser(user User) error {
+func (p BoltProvider) deleteUser(user *User) error {
 	return p.dbHandle.Update(func(tx *bolt.Tx) error {
 		bucket, idxBucket, err := getBuckets(tx)
 		if err != nil {
@@ -567,8 +567,8 @@ func (p BoltProvider) getFolderByPath(name string) (vfs.BaseVirtualFolder, error
 	return folder, err
 }
 
-func (p BoltProvider) addFolder(folder vfs.BaseVirtualFolder) error {
-	err := validateFolder(&folder)
+func (p BoltProvider) addFolder(folder *vfs.BaseVirtualFolder) error {
+	err := validateFolder(folder)
 	if err != nil {
 		return err
 	}
@@ -580,12 +580,12 @@ func (p BoltProvider) addFolder(folder vfs.BaseVirtualFolder) error {
 		if f := bucket.Get([]byte(folder.MappedPath)); f != nil {
 			return fmt.Errorf("folder %v already exists", folder.MappedPath)
 		}
-		_, err = addFolderInternal(folder, bucket)
+		_, err = addFolderInternal(*folder, bucket)
 		return err
 	})
 }
 
-func (p BoltProvider) deleteFolder(folder vfs.BaseVirtualFolder) error {
+func (p BoltProvider) deleteFolder(folder *vfs.BaseVirtualFolder) error {
 	return p.dbHandle.Update(func(tx *bolt.Tx) error {
 		bucket, err := getFolderBucket(tx)
 		if err != nil {
@@ -816,7 +816,7 @@ func addFolderInternal(folder vfs.BaseVirtualFolder, bucket *bolt.Bucket) (vfs.B
 	return folder, err
 }
 
-func addUserToFolderMapping(folder vfs.VirtualFolder, user User, bucket *bolt.Bucket) error {
+func addUserToFolderMapping(folder vfs.VirtualFolder, user *User, bucket *bolt.Bucket) error {
 	var baseFolder vfs.BaseVirtualFolder
 	var err error
 	if f := bucket.Get([]byte(folder.MappedPath)); f == nil {
@@ -842,7 +842,7 @@ func addUserToFolderMapping(folder vfs.VirtualFolder, user User, bucket *bolt.Bu
 	return err
 }
 
-func removeUserFromFolderMapping(folder vfs.VirtualFolder, user User, bucket *bolt.Bucket) error {
+func removeUserFromFolderMapping(folder vfs.VirtualFolder, user *User, bucket *bolt.Bucket) error {
 	var f []byte
 	if f = bucket.Get([]byte(folder.MappedPath)); f == nil {
 		// the folder does not exists so there is no associated user
@@ -940,7 +940,7 @@ func updateDatabaseFrom1To2(dbHandle *bolt.DB) error {
 			return err
 		}
 		user.Status = 1
-		err = provider.updateUser(user)
+		err = provider.updateUser(&user)
 		if err != nil {
 			return err
 		}
@@ -994,7 +994,8 @@ func updateDatabaseFrom2To3(dbHandle *bolt.DB) error {
 	}
 
 	for _, user := range users {
-		err = provider.updateUser(user)
+		user := user
+		err = provider.updateUser(&user)
 		if err != nil {
 			return err
 		}
@@ -1053,7 +1054,7 @@ func updateDatabaseFrom3To4(dbHandle *bolt.DB) error {
 			}
 		}
 		user.VirtualFolders = folders
-		err = provider.updateUser(user)
+		err = provider.updateUser(&user)
 		providerLog(logger.LevelInfo, "number of virtual folders to restore %v, user %#v, error: %v", len(user.VirtualFolders),
 			user.Username, err)
 		if err != nil {

+ 41 - 38
dataprovider/dataprovider.go

@@ -367,17 +367,17 @@ type Provider interface {
 	updateQuota(username string, filesAdd int, sizeAdd int64, reset bool) error
 	getUsedQuota(username string) (int, int64, error)
 	userExists(username string) (User, error)
-	addUser(user User) error
-	updateUser(user User) error
-	deleteUser(user User) error
+	addUser(user *User) error
+	updateUser(user *User) error
+	deleteUser(user *User) error
 	getUsers(limit int, offset int, order string, username string) ([]User, error)
 	dumpUsers() ([]User, error)
 	getUserByID(ID int64) (User, error)
 	updateLastLogin(username string) error
 	getFolders(limit, offset int, order, folderPath string) ([]vfs.BaseVirtualFolder, error)
 	getFolderByPath(mappedPath string) (vfs.BaseVirtualFolder, error)
-	addFolder(folder vfs.BaseVirtualFolder) error
-	deleteFolder(folder vfs.BaseVirtualFolder) error
+	addFolder(folder *vfs.BaseVirtualFolder) error
+	deleteFolder(folder *vfs.BaseVirtualFolder) error
 	updateFolderQuota(mappedPath string, filesAdd int, sizeAdd int64, reset bool) error
 	getUsedFolderQuota(mappedPath string) (int, int64, error)
 	dumpFolders() ([]vfs.BaseVirtualFolder, error)
@@ -439,16 +439,16 @@ func Initialize(cnf Config, basePath string) error {
 
 func validateHooks() error {
 	var hooks []string
-	if len(config.PreLoginHook) > 0 && !strings.HasPrefix(config.PreLoginHook, "http") {
+	if config.PreLoginHook != "" && !strings.HasPrefix(config.PreLoginHook, "http") {
 		hooks = append(hooks, config.PreLoginHook)
 	}
-	if len(config.ExternalAuthHook) > 0 && !strings.HasPrefix(config.ExternalAuthHook, "http") {
+	if config.ExternalAuthHook != "" && !strings.HasPrefix(config.ExternalAuthHook, "http") {
 		hooks = append(hooks, config.ExternalAuthHook)
 	}
-	if len(config.PostLoginHook) > 0 && !strings.HasPrefix(config.PostLoginHook, "http") {
+	if config.PostLoginHook != "" && !strings.HasPrefix(config.PostLoginHook, "http") {
 		hooks = append(hooks, config.PostLoginHook)
 	}
-	if len(config.CheckPasswordHook) > 0 && !strings.HasPrefix(config.CheckPasswordHook, "http") {
+	if config.CheckPasswordHook != "" && !strings.HasPrefix(config.CheckPasswordHook, "http") {
 		hooks = append(hooks, config.CheckPasswordHook)
 	}
 
@@ -527,14 +527,14 @@ func RevertDatabase(cnf Config, basePath string, targetVersion int) error {
 
 // CheckUserAndPass retrieves the SFTP user with the given username and password if a match is found or an error
 func CheckUserAndPass(username, password, ip, protocol string) (User, error) {
-	if len(config.ExternalAuthHook) > 0 && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&1 != 0) {
+	if config.ExternalAuthHook != "" && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&1 != 0) {
 		user, err := doExternalAuth(username, password, nil, "", ip, protocol)
 		if err != nil {
 			return user, err
 		}
 		return checkUserAndPass(user, password, ip, protocol)
 	}
-	if len(config.PreLoginHook) > 0 {
+	if config.PreLoginHook != "" {
 		user, err := executePreLoginHook(username, LoginMethodPassword, ip, protocol)
 		if err != nil {
 			return user, err
@@ -546,14 +546,14 @@ func CheckUserAndPass(username, password, ip, protocol string) (User, error) {
 
 // CheckUserAndPubKey retrieves the SFTP user with the given username and public key if a match is found or an error
 func CheckUserAndPubKey(username string, pubKey []byte, ip, protocol string) (User, string, error) {
-	if len(config.ExternalAuthHook) > 0 && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&2 != 0) {
+	if config.ExternalAuthHook != "" && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&2 != 0) {
 		user, err := doExternalAuth(username, "", pubKey, "", ip, protocol)
 		if err != nil {
 			return user, "", err
 		}
 		return checkUserAndPubKey(user, pubKey)
 	}
-	if len(config.PreLoginHook) > 0 {
+	if config.PreLoginHook != "" {
 		user, err := executePreLoginHook(username, SSHLoginMethodPublicKey, ip, protocol)
 		if err != nil {
 			return user, "", err
@@ -568,9 +568,9 @@ func CheckUserAndPubKey(username string, pubKey []byte, ip, protocol string) (Us
 func CheckKeyboardInteractiveAuth(username, authHook string, client ssh.KeyboardInteractiveChallenge, ip, protocol string) (User, error) {
 	var user User
 	var err error
-	if len(config.ExternalAuthHook) > 0 && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&4 != 0) {
+	if config.ExternalAuthHook != "" && (config.ExternalAuthScope == 0 || config.ExternalAuthScope&4 != 0) {
 		user, err = doExternalAuth(username, "", nil, "1", ip, protocol)
-	} else if len(config.PreLoginHook) > 0 {
+	} else if config.PreLoginHook != "" {
 		user, err = executePreLoginHook(username, SSHLoginMethodKeyboardInteractive, ip, protocol)
 	} else {
 		user, err = provider.userExists(username)
@@ -653,41 +653,41 @@ func UserExists(username string) (User, error) {
 
 // AddUser adds a new SFTPGo user.
 // ManageUsers configuration must be set to 1 to enable this method
-func AddUser(user User) error {
+func AddUser(user *User) error {
 	if config.ManageUsers == 0 {
 		return &MethodDisabledError{err: manageUsersDisabledError}
 	}
 	err := provider.addUser(user)
 	if err == nil {
-		go executeAction(operationAdd, user)
+		go executeAction(operationAdd, *user)
 	}
 	return err
 }
 
 // UpdateUser updates an existing SFTPGo user.
 // ManageUsers configuration must be set to 1 to enable this method
-func UpdateUser(user User) error {
+func UpdateUser(user *User) error {
 	if config.ManageUsers == 0 {
 		return &MethodDisabledError{err: manageUsersDisabledError}
 	}
 	err := provider.updateUser(user)
 	if err == nil {
 		RemoveCachedWebDAVUser(user.Username)
-		go executeAction(operationUpdate, user)
+		go executeAction(operationUpdate, *user)
 	}
 	return err
 }
 
 // DeleteUser deletes an existing SFTPGo user.
 // ManageUsers configuration must be set to 1 to enable this method
-func DeleteUser(user User) error {
+func DeleteUser(user *User) error {
 	if config.ManageUsers == 0 {
 		return &MethodDisabledError{err: manageUsersDisabledError}
 	}
 	err := provider.deleteUser(user)
 	if err == nil {
 		RemoveCachedWebDAVUser(user.Username)
-		go executeAction(operationDelete, user)
+		go executeAction(operationDelete, *user)
 	}
 	return err
 }
@@ -711,7 +711,7 @@ func GetUserByID(ID int64) (User, error) {
 
 // AddFolder adds a new virtual folder.
 // ManageUsers configuration must be set to 1 to enable this method
-func AddFolder(folder vfs.BaseVirtualFolder) error {
+func AddFolder(folder *vfs.BaseVirtualFolder) error {
 	if config.ManageUsers == 0 {
 		return &MethodDisabledError{err: manageUsersDisabledError}
 	}
@@ -720,7 +720,7 @@ func AddFolder(folder vfs.BaseVirtualFolder) error {
 
 // DeleteFolder deletes an existing folder.
 // ManageUsers configuration must be set to 1 to enable this method
-func DeleteFolder(folder vfs.BaseVirtualFolder) error {
+func DeleteFolder(folder *vfs.BaseVirtualFolder) error {
 	if config.ManageUsers == 0 {
 		return &MethodDisabledError{err: manageUsersDisabledError}
 	}
@@ -1303,7 +1303,7 @@ func validateUser(user *User) error {
 	return nil
 }
 
-func checkLoginConditions(user User) error {
+func checkLoginConditions(user *User) error {
 	if user.Status < 1 {
 		return fmt.Errorf("user %#v is disabled", user.Username)
 	}
@@ -1344,11 +1344,11 @@ func isPasswordOK(user *User, password string) (bool, error) {
 }
 
 func checkUserAndPass(user User, password, ip, protocol string) (User, error) {
-	err := checkLoginConditions(user)
+	err := checkLoginConditions(&user)
 	if err != nil {
 		return user, err
 	}
-	if len(user.Password) == 0 {
+	if user.Password == "" {
 		return user, errors.New("Credentials cannot be null or empty")
 	}
 	hookResponse, err := executeCheckPasswordHook(user.Username, password, ip, protocol)
@@ -1378,7 +1378,7 @@ func checkUserAndPass(user User, password, ip, protocol string) (User, error) {
 }
 
 func checkUserAndPubKey(user User, pubKey []byte) (User, string, error) {
-	err := checkLoginConditions(user)
+	err := checkLoginConditions(&user)
 	if err != nil {
 		return user, "", err
 	}
@@ -1731,7 +1731,7 @@ func doKeyboardInteractiveAuth(user User, authHook string, client ssh.KeyboardIn
 	if authResult != 1 {
 		return user, fmt.Errorf("keyboard interactive auth failed, result: %v", authResult)
 	}
-	err = checkLoginConditions(user)
+	err = checkLoginConditions(&user)
 	if err != nil {
 		return user, err
 	}
@@ -1739,7 +1739,7 @@ func doKeyboardInteractiveAuth(user User, authHook string, client ssh.KeyboardIn
 }
 
 func isCheckPasswordHookDefined(protocol string) bool {
-	if len(config.CheckPasswordHook) == 0 {
+	if config.CheckPasswordHook == "" {
 		return false
 	}
 	if config.CheckPasswordScope == 0 {
@@ -1900,20 +1900,23 @@ func executePreLoginHook(username, loginMethod, ip, protocol string) (User, erro
 	u.LastQuotaUpdate = userLastQuotaUpdate
 	u.LastLogin = userLastLogin
 	if userID == 0 {
-		err = provider.addUser(u)
+		err = provider.addUser(&u)
 	} else {
-		err = provider.updateUser(u)
+		err = provider.updateUser(&u)
 	}
 	if err != nil {
 		return u, err
 	}
 	providerLog(logger.LevelDebug, "user %#v added/updated from pre-login hook response, id: %v", username, userID)
-	return provider.userExists(username)
+	if userID == 0 {
+		return provider.userExists(username)
+	}
+	return u, nil
 }
 
 // ExecutePostLoginHook executes the post login hook if defined
 func ExecutePostLoginHook(username, loginMethod, ip, protocol string, err error) {
-	if len(config.PostLoginHook) == 0 {
+	if config.PostLoginHook == "" {
 		return
 	}
 	if config.PostLoginScope == 1 && err == nil {
@@ -2047,7 +2050,7 @@ func doExternalAuth(username, password string, pubKey []byte, keyboardInteractiv
 	if len(pkey) > 0 && !utils.IsStringPrefixInSlice(pkey, user.PublicKeys) {
 		user.PublicKeys = append(user.PublicKeys, pkey)
 	}
-	// some users want to map multiple login usernames with a single SGTPGo account
+	// some users want to map multiple login usernames with a single SFTPGo account
 	// for example an SFTP user logins using "user1" or "user2" and the external auth
 	// returns "user" in both cases, so we use the username returned from
 	// external auth and not the one used to login
@@ -2058,10 +2061,10 @@ func doExternalAuth(username, password string, pubKey []byte, keyboardInteractiv
 		user.UsedQuotaFiles = u.UsedQuotaFiles
 		user.LastQuotaUpdate = u.LastQuotaUpdate
 		user.LastLogin = u.LastLogin
-		err = provider.updateUser(user)
-	} else {
-		err = provider.addUser(user)
+		err = provider.updateUser(&user)
+		return user, err
 	}
+	err = provider.addUser(&user)
 	if err != nil {
 		return user, err
 	}
@@ -2174,7 +2177,7 @@ func CacheWebDAVUser(cachedUser *CachedUser, maxSize int) {
 
 		webDAVUsersCache.Range(func(k, v interface{}) bool {
 			cacheSize++
-			if len(userToRemove) == 0 {
+			if userToRemove == "" {
 				userToRemove = k.(string)
 				expirationTime = v.(*CachedUser).Expiration
 				return true

+ 20 - 17
dataprovider/memory.go

@@ -88,7 +88,7 @@ func (p MemoryProvider) close() error {
 
 func (p MemoryProvider) validateUserAndPass(username, password, ip, protocol string) (User, error) {
 	var user User
-	if len(password) == 0 {
+	if password == "" {
 		return user, errors.New("Credentials cannot be null or empty")
 	}
 	user, err := p.userExists(username)
@@ -178,13 +178,13 @@ func (p MemoryProvider) getUsedQuota(username string) (int, int64, error) {
 	return user.UsedQuotaFiles, user.UsedQuotaSize, err
 }
 
-func (p MemoryProvider) addUser(user User) error {
+func (p MemoryProvider) addUser(user *User) error {
 	p.dbHandle.Lock()
 	defer p.dbHandle.Unlock()
 	if p.dbHandle.isClosed {
 		return errMemoryProviderClosed
 	}
-	err := validateUser(&user)
+	err := validateUser(user)
 	if err != nil {
 		return err
 	}
@@ -198,20 +198,20 @@ func (p MemoryProvider) addUser(user User) error {
 	user.UsedQuotaFiles = 0
 	user.LastLogin = 0
 	user.VirtualFolders = p.joinVirtualFoldersFields(user)
-	p.dbHandle.users[user.Username] = user
+	p.dbHandle.users[user.Username] = user.getACopy()
 	p.dbHandle.usersIdx[user.ID] = user.Username
 	p.dbHandle.usernames = append(p.dbHandle.usernames, user.Username)
 	sort.Strings(p.dbHandle.usernames)
 	return nil
 }
 
-func (p MemoryProvider) updateUser(user User) error {
+func (p MemoryProvider) updateUser(user *User) error {
 	p.dbHandle.Lock()
 	defer p.dbHandle.Unlock()
 	if p.dbHandle.isClosed {
 		return errMemoryProviderClosed
 	}
-	err := validateUser(&user)
+	err := validateUser(user)
 	if err != nil {
 		return err
 	}
@@ -227,11 +227,12 @@ func (p MemoryProvider) updateUser(user User) error {
 	user.UsedQuotaSize = u.UsedQuotaSize
 	user.UsedQuotaFiles = u.UsedQuotaFiles
 	user.LastLogin = u.LastLogin
-	p.dbHandle.users[user.Username] = user
+	// pre-login and external auth hook will use the passed *user so save a copy
+	p.dbHandle.users[user.Username] = user.getACopy()
 	return nil
 }
 
-func (p MemoryProvider) deleteUser(user User) error {
+func (p MemoryProvider) deleteUser(user *User) error {
 	p.dbHandle.Lock()
 	defer p.dbHandle.Unlock()
 	if p.dbHandle.isClosed {
@@ -247,7 +248,7 @@ func (p MemoryProvider) deleteUser(user User) error {
 	delete(p.dbHandle.users, user.Username)
 	delete(p.dbHandle.usersIdx, user.ID)
 	// this could be more efficient
-	p.dbHandle.usernames = []string{}
+	p.dbHandle.usernames = make([]string, 0, len(p.dbHandle.users))
 	for username := range p.dbHandle.users {
 		p.dbHandle.usernames = append(p.dbHandle.usernames, username)
 	}
@@ -396,7 +397,7 @@ func (p MemoryProvider) getUsedFolderQuota(mappedPath string) (int, int64, error
 	return folder.UsedQuotaFiles, folder.UsedQuotaSize, err
 }
 
-func (p MemoryProvider) joinVirtualFoldersFields(user User) []vfs.VirtualFolder {
+func (p MemoryProvider) joinVirtualFoldersFields(user *User) []vfs.VirtualFolder {
 	var folders []vfs.VirtualFolder
 	for _, folder := range user.VirtualFolders {
 		f, err := p.addOrGetFolderInternal(folder.MappedPath, user.Username, folder.UsedQuotaSize, folder.UsedQuotaFiles,
@@ -522,13 +523,13 @@ func (p MemoryProvider) getFolderByPath(mappedPath string) (vfs.BaseVirtualFolde
 	return p.folderExistsInternal(mappedPath)
 }
 
-func (p MemoryProvider) addFolder(folder vfs.BaseVirtualFolder) error {
+func (p MemoryProvider) addFolder(folder *vfs.BaseVirtualFolder) error {
 	p.dbHandle.Lock()
 	defer p.dbHandle.Unlock()
 	if p.dbHandle.isClosed {
 		return errMemoryProviderClosed
 	}
-	err := validateFolder(&folder)
+	err := validateFolder(folder)
 	if err != nil {
 		return err
 	}
@@ -537,13 +538,13 @@ func (p MemoryProvider) addFolder(folder vfs.BaseVirtualFolder) error {
 		return fmt.Errorf("folder %#v already exists", folder.MappedPath)
 	}
 	folder.ID = p.getNextFolderID()
-	p.dbHandle.vfolders[folder.MappedPath] = folder
+	p.dbHandle.vfolders[folder.MappedPath] = *folder
 	p.dbHandle.vfoldersPaths = append(p.dbHandle.vfoldersPaths, folder.MappedPath)
 	sort.Strings(p.dbHandle.vfoldersPaths)
 	return nil
 }
 
-func (p MemoryProvider) deleteFolder(folder vfs.BaseVirtualFolder) error {
+func (p MemoryProvider) deleteFolder(folder *vfs.BaseVirtualFolder) error {
 	p.dbHandle.Lock()
 	defer p.dbHandle.Unlock()
 	if p.dbHandle.isClosed {
@@ -644,8 +645,9 @@ func (p MemoryProvider) reloadConfig() error {
 			logger.Debug(logSender, "", "folder %#v already exists, restore not needed", folder.MappedPath)
 			continue
 		}
+		folder := folder // pin
 		folder.Users = nil
-		err = p.addFolder(folder)
+		err = p.addFolder(&folder)
 		if err != nil {
 			providerLog(logger.LevelWarn, "error adding folder %#v: %v", folder.MappedPath, err)
 			return err
@@ -653,15 +655,16 @@ func (p MemoryProvider) reloadConfig() error {
 	}
 	for _, user := range dump.Users {
 		u, err := p.userExists(user.Username)
+		user := user // pin
 		if err == nil {
 			user.ID = u.ID
-			err = p.updateUser(user)
+			err = p.updateUser(&user)
 			if err != nil {
 				providerLog(logger.LevelWarn, "error updating user %#v: %v", user.Username, err)
 				return err
 			}
 		} else {
-			err = p.addUser(user)
+			err = p.addUser(&user)
 			if err != nil {
 				providerLog(logger.LevelWarn, "error adding user %#v: %v", user.Username, err)
 				return err

+ 6 - 6
dataprovider/mysql.go

@@ -74,7 +74,7 @@ func initializeMySQLProvider() error {
 }
 func getMySQLConnectionString(redactedPwd bool) string {
 	var connectionString string
-	if len(config.ConnectionString) == 0 {
+	if config.ConnectionString == "" {
 		password := config.Password
 		if redactedPwd {
 			password = "[redacted]"
@@ -119,15 +119,15 @@ func (p MySQLProvider) userExists(username string) (User, error) {
 	return sqlCommonCheckUserExists(username, p.dbHandle)
 }
 
-func (p MySQLProvider) addUser(user User) error {
+func (p MySQLProvider) addUser(user *User) error {
 	return sqlCommonAddUser(user, p.dbHandle)
 }
 
-func (p MySQLProvider) updateUser(user User) error {
+func (p MySQLProvider) updateUser(user *User) error {
 	return sqlCommonUpdateUser(user, p.dbHandle)
 }
 
-func (p MySQLProvider) deleteUser(user User) error {
+func (p MySQLProvider) deleteUser(user *User) error {
 	return sqlCommonDeleteUser(user, p.dbHandle)
 }
 
@@ -153,11 +153,11 @@ func (p MySQLProvider) getFolderByPath(mappedPath string) (vfs.BaseVirtualFolder
 	return sqlCommonCheckFolderExists(ctx, mappedPath, p.dbHandle)
 }
 
-func (p MySQLProvider) addFolder(folder vfs.BaseVirtualFolder) error {
+func (p MySQLProvider) addFolder(folder *vfs.BaseVirtualFolder) error {
 	return sqlCommonAddFolder(folder, p.dbHandle)
 }
 
-func (p MySQLProvider) deleteFolder(folder vfs.BaseVirtualFolder) error {
+func (p MySQLProvider) deleteFolder(folder *vfs.BaseVirtualFolder) error {
 	return sqlCommonDeleteFolder(folder, p.dbHandle)
 }
 

+ 6 - 6
dataprovider/pgsql.go

@@ -75,7 +75,7 @@ func initializePGSQLProvider() error {
 
 func getPGSQLConnectionString(redactedPwd bool) string {
 	var connectionString string
-	if len(config.ConnectionString) == 0 {
+	if config.ConnectionString == "" {
 		password := config.Password
 		if redactedPwd {
 			password = "[redacted]"
@@ -120,15 +120,15 @@ func (p PGSQLProvider) userExists(username string) (User, error) {
 	return sqlCommonCheckUserExists(username, p.dbHandle)
 }
 
-func (p PGSQLProvider) addUser(user User) error {
+func (p PGSQLProvider) addUser(user *User) error {
 	return sqlCommonAddUser(user, p.dbHandle)
 }
 
-func (p PGSQLProvider) updateUser(user User) error {
+func (p PGSQLProvider) updateUser(user *User) error {
 	return sqlCommonUpdateUser(user, p.dbHandle)
 }
 
-func (p PGSQLProvider) deleteUser(user User) error {
+func (p PGSQLProvider) deleteUser(user *User) error {
 	return sqlCommonDeleteUser(user, p.dbHandle)
 }
 
@@ -154,11 +154,11 @@ func (p PGSQLProvider) getFolderByPath(mappedPath string) (vfs.BaseVirtualFolder
 	return sqlCommonCheckFolderExists(ctx, mappedPath, p.dbHandle)
 }
 
-func (p PGSQLProvider) addFolder(folder vfs.BaseVirtualFolder) error {
+func (p PGSQLProvider) addFolder(folder *vfs.BaseVirtualFolder) error {
 	return sqlCommonAddFolder(folder, p.dbHandle)
 }
 
-func (p PGSQLProvider) deleteFolder(folder vfs.BaseVirtualFolder) error {
+func (p PGSQLProvider) deleteFolder(folder *vfs.BaseVirtualFolder) error {
 	return sqlCommonDeleteFolder(folder, p.dbHandle)
 }
 

+ 16 - 16
dataprovider/sqlcommon.go

@@ -48,7 +48,7 @@ func getUserByUsername(username string, dbHandle sqlQuerier) (User, error) {
 
 func sqlCommonValidateUserAndPass(username, password, ip, protocol string, dbHandle *sql.DB) (User, error) {
 	var user User
-	if len(password) == 0 {
+	if password == "" {
 		return user, errors.New("Credentials cannot be null or empty")
 	}
 	user, err := getUserByUsername(username, dbHandle)
@@ -177,8 +177,8 @@ func sqlCommonCheckUserExists(username string, dbHandle *sql.DB) (User, error) {
 	return getUserWithVirtualFolders(user, dbHandle)
 }
 
-func sqlCommonAddUser(user User, dbHandle *sql.DB) error {
-	err := validateUser(&user)
+func sqlCommonAddUser(user *User, dbHandle *sql.DB) error {
+	err := validateUser(user)
 	if err != nil {
 		return err
 	}
@@ -231,8 +231,8 @@ func sqlCommonAddUser(user User, dbHandle *sql.DB) error {
 	return tx.Commit()
 }
 
-func sqlCommonUpdateUser(user User, dbHandle *sql.DB) error {
-	err := validateUser(&user)
+func sqlCommonUpdateUser(user *User, dbHandle *sql.DB) error {
+	err := validateUser(user)
 	if err != nil {
 		return err
 	}
@@ -285,7 +285,7 @@ func sqlCommonUpdateUser(user User, dbHandle *sql.DB) error {
 	return tx.Commit()
 }
 
-func sqlCommonDeleteUser(user User, dbHandle *sql.DB) error {
+func sqlCommonDeleteUser(user *User, dbHandle *sql.DB) error {
 	ctx, cancel := context.WithTimeout(context.Background(), defaultSQLQueryTimeout)
 	defer cancel()
 	q := getDeleteUserQuery()
@@ -470,7 +470,7 @@ func sqlCommonCheckFolderExists(ctx context.Context, name string, dbHandle sqlQu
 func sqlCommonAddOrGetFolder(ctx context.Context, name string, usedQuotaSize int64, usedQuotaFiles int, lastQuotaUpdate int64, dbHandle sqlQuerier) (vfs.BaseVirtualFolder, error) {
 	folder, err := sqlCommonCheckFolderExists(ctx, name, dbHandle)
 	if _, ok := err.(*RecordNotFoundError); ok {
-		f := vfs.BaseVirtualFolder{
+		f := &vfs.BaseVirtualFolder{
 			MappedPath:      name,
 			UsedQuotaSize:   usedQuotaSize,
 			UsedQuotaFiles:  usedQuotaFiles,
@@ -485,8 +485,8 @@ func sqlCommonAddOrGetFolder(ctx context.Context, name string, usedQuotaSize int
 	return folder, err
 }
 
-func sqlCommonAddFolder(folder vfs.BaseVirtualFolder, dbHandle sqlQuerier) error {
-	err := validateFolder(&folder)
+func sqlCommonAddFolder(folder *vfs.BaseVirtualFolder, dbHandle sqlQuerier) error {
+	err := validateFolder(folder)
 	if err != nil {
 		return err
 	}
@@ -503,7 +503,7 @@ func sqlCommonAddFolder(folder vfs.BaseVirtualFolder, dbHandle sqlQuerier) error
 	return err
 }
 
-func sqlCommonDeleteFolder(folder vfs.BaseVirtualFolder, dbHandle sqlQuerier) error {
+func sqlCommonDeleteFolder(folder *vfs.BaseVirtualFolder, dbHandle sqlQuerier) error {
 	ctx, cancel := context.WithTimeout(context.Background(), defaultSQLQueryTimeout)
 	defer cancel()
 	q := getDeleteFolderQuery()
@@ -585,7 +585,7 @@ func sqlCommonGetFolders(limit, offset int, order, folderPath string, dbHandle s
 	return getVirtualFoldersWithUsers(folders, dbHandle)
 }
 
-func sqlCommonClearFolderMapping(ctx context.Context, user User, dbHandle sqlQuerier) error {
+func sqlCommonClearFolderMapping(ctx context.Context, user *User, dbHandle sqlQuerier) error {
 	q := getClearFolderMappingQuery()
 	stmt, err := dbHandle.PrepareContext(ctx, q)
 	if err != nil {
@@ -597,7 +597,7 @@ func sqlCommonClearFolderMapping(ctx context.Context, user User, dbHandle sqlQue
 	return err
 }
 
-func sqlCommonAddFolderMapping(ctx context.Context, user User, folder vfs.VirtualFolder, dbHandle sqlQuerier) error {
+func sqlCommonAddFolderMapping(ctx context.Context, user *User, folder vfs.VirtualFolder, dbHandle sqlQuerier) error {
 	q := getAddFolderMappingQuery()
 	stmt, err := dbHandle.PrepareContext(ctx, q)
 	if err != nil {
@@ -609,7 +609,7 @@ func sqlCommonAddFolderMapping(ctx context.Context, user User, folder vfs.Virtua
 	return err
 }
 
-func generateVirtualFoldersMapping(ctx context.Context, user User, dbHandle sqlQuerier) error {
+func generateVirtualFoldersMapping(ctx context.Context, user *User, dbHandle sqlQuerier) error {
 	err := sqlCommonClearFolderMapping(ctx, user, dbHandle)
 	if err != nil {
 		return err
@@ -813,7 +813,7 @@ func sqlCommonExecSQLAndUpdateDBVersion(dbHandle *sql.DB, sql []string, newVersi
 		return err
 	}
 	for _, q := range sql {
-		if len(strings.TrimSpace(q)) == 0 {
+		if strings.TrimSpace(q) == "" {
 			continue
 		}
 		_, err = tx.ExecContext(ctx, q)
@@ -892,7 +892,7 @@ func sqlCommonRestoreCompatVirtualFolders(ctx context.Context, users []userCompa
 				QuotaSize:         quotaSize,
 				QuotaFiles:        quotaFiles,
 			}
-			err = sqlCommonAddFolderMapping(ctx, u, f, dbHandle)
+			err = sqlCommonAddFolderMapping(ctx, &u, f, dbHandle)
 			if err != nil {
 				providerLog(logger.LevelWarn, "error adding virtual folder mapping for user %#v: %v", user.Username, err)
 				return foldersToScan, err
@@ -923,7 +923,7 @@ func sqlCommonUpdateDatabaseFrom3To4(sqlV4 string, dbHandle *sql.DB) error {
 		return err
 	}
 	for _, q := range strings.Split(sql, ";") {
-		if len(strings.TrimSpace(q)) == 0 {
+		if strings.TrimSpace(q) == "" {
 			continue
 		}
 		_, err = tx.ExecContext(ctx, q)

+ 6 - 6
dataprovider/sqlite.go

@@ -93,7 +93,7 @@ func initializeSQLiteProvider(basePath string) error {
 	var err error
 	var connectionString string
 	logSender = fmt.Sprintf("dataprovider_%v", SQLiteDataProviderName)
-	if len(config.ConnectionString) == 0 {
+	if config.ConnectionString == "" {
 		dbPath := config.Name
 		if !utils.IsFileInputValid(dbPath) {
 			return fmt.Errorf("Invalid database path: %#v", dbPath)
@@ -149,15 +149,15 @@ func (p SQLiteProvider) userExists(username string) (User, error) {
 	return sqlCommonCheckUserExists(username, p.dbHandle)
 }
 
-func (p SQLiteProvider) addUser(user User) error {
+func (p SQLiteProvider) addUser(user *User) error {
 	return sqlCommonAddUser(user, p.dbHandle)
 }
 
-func (p SQLiteProvider) updateUser(user User) error {
+func (p SQLiteProvider) updateUser(user *User) error {
 	return sqlCommonUpdateUser(user, p.dbHandle)
 }
 
-func (p SQLiteProvider) deleteUser(user User) error {
+func (p SQLiteProvider) deleteUser(user *User) error {
 	return sqlCommonDeleteUser(user, p.dbHandle)
 }
 
@@ -183,11 +183,11 @@ func (p SQLiteProvider) getFolderByPath(mappedPath string) (vfs.BaseVirtualFolde
 	return sqlCommonCheckFolderExists(ctx, mappedPath, p.dbHandle)
 }
 
-func (p SQLiteProvider) addFolder(folder vfs.BaseVirtualFolder) error {
+func (p SQLiteProvider) addFolder(folder *vfs.BaseVirtualFolder) error {
 	return sqlCommonAddFolder(folder, p.dbHandle)
 }
 
-func (p SQLiteProvider) deleteFolder(folder vfs.BaseVirtualFolder) error {
+func (p SQLiteProvider) deleteFolder(folder *vfs.BaseVirtualFolder) error {
 	return sqlCommonDeleteFolder(folder, p.dbHandle)
 }
 

+ 3 - 3
httpd/api_folder.go

@@ -63,7 +63,7 @@ func addFolder(w http.ResponseWriter, r *http.Request) {
 		sendAPIResponse(w, r, err, "", http.StatusBadRequest)
 		return
 	}
-	err = dataprovider.AddFolder(folder)
+	err = dataprovider.AddFolder(&folder)
 	if err == nil {
 		folder, err = dataprovider.GetFolderByPath(folder.MappedPath)
 		if err == nil {
@@ -81,7 +81,7 @@ func deleteFolderByPath(w http.ResponseWriter, r *http.Request) {
 	if _, ok := r.URL.Query()["folder_path"]; ok {
 		folderPath = r.URL.Query().Get("folder_path")
 	}
-	if len(folderPath) == 0 {
+	if folderPath == "" {
 		err := errors.New("a non-empty folder path is required")
 		sendAPIResponse(w, r, err, "", http.StatusBadRequest)
 		return
@@ -92,7 +92,7 @@ func deleteFolderByPath(w http.ResponseWriter, r *http.Request) {
 		sendAPIResponse(w, r, err, "", getRespStatus(err))
 		return
 	}
-	err = dataprovider.DeleteFolder(folder)
+	err = dataprovider.DeleteFolder(&folder)
 	if err != nil {
 		sendAPIResponse(w, r, err, "", http.StatusInternalServerError)
 	} else {

+ 6 - 4
httpd/api_maintenance.go

@@ -25,7 +25,7 @@ func dumpData(w http.ResponseWriter, r *http.Request) {
 	if _, ok := r.URL.Query()["indent"]; ok {
 		indent = strings.TrimSpace(r.URL.Query().Get("indent"))
 	}
-	if len(outputFile) == 0 {
+	if outputFile == "" {
 		sendAPIResponse(w, r, errors.New("Invalid or missing output_file"), "", http.StatusBadRequest)
 		return
 	}
@@ -147,8 +147,9 @@ func RestoreFolders(folders []vfs.BaseVirtualFolder, inputFile string, scanQuota
 			logger.Debug(logSender, "", "folder %#v already exists, restore not needed", folder.MappedPath)
 			continue
 		}
+		folder := folder // pin
 		folder.Users = nil
-		err = dataprovider.AddFolder(folder)
+		err = dataprovider.AddFolder(&folder)
 		logger.Debug(logSender, "", "adding new folder: %+v, dump file: %#v, error: %v", folder, inputFile, err)
 		if err != nil {
 			return err
@@ -166,6 +167,7 @@ func RestoreFolders(folders []vfs.BaseVirtualFolder, inputFile string, scanQuota
 // RestoreUsers restores the specified users
 func RestoreUsers(users []dataprovider.User, inputFile string, mode, scanQuota int) error {
 	for _, user := range users {
+		user := user // pin
 		u, err := dataprovider.UserExists(user.Username)
 		if err == nil {
 			if mode == 1 {
@@ -173,14 +175,14 @@ func RestoreUsers(users []dataprovider.User, inputFile string, mode, scanQuota i
 				continue
 			}
 			user.ID = u.ID
-			err = dataprovider.UpdateUser(user)
+			err = dataprovider.UpdateUser(&user)
 			user.Password = "[redacted]"
 			logger.Debug(logSender, "", "restoring existing user: %+v, dump file: %#v, error: %v", user, inputFile, err)
 			if mode == 2 && err == nil {
 				disconnectUser(user.Username)
 			}
 		} else {
-			err = dataprovider.AddUser(user)
+			err = dataprovider.AddUser(&user)
 			user.Password = "[redacted]"
 			logger.Debug(logSender, "", "adding new user: %+v, dump file: %#v, error: %v", user, inputFile, err)
 		}

+ 3 - 3
httpd/api_user.go

@@ -115,7 +115,7 @@ func addUser(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 	}
-	err = dataprovider.AddUser(user)
+	err = dataprovider.AddUser(&user)
 	if err == nil {
 		user, err = dataprovider.UserExists(user.Username)
 		if err == nil {
@@ -181,7 +181,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
 		sendAPIResponse(w, r, err, "user ID in request body does not match user ID in path parameter", http.StatusBadRequest)
 		return
 	}
-	err = dataprovider.UpdateUser(user)
+	err = dataprovider.UpdateUser(&user)
 	if err != nil {
 		sendAPIResponse(w, r, err, "", getRespStatus(err))
 	} else {
@@ -204,7 +204,7 @@ func deleteUser(w http.ResponseWriter, r *http.Request) {
 		sendAPIResponse(w, r, err, "", getRespStatus(err))
 		return
 	}
-	err = dataprovider.DeleteUser(user)
+	err = dataprovider.DeleteUser(&user)
 	if err != nil {
 		sendAPIResponse(w, r, err, "", http.StatusInternalServerError)
 	} else {

+ 2 - 2
httpd/httpd.go

@@ -129,10 +129,10 @@ func (c Conf) Initialize(configDir string) error {
 	staticFilesPath := getConfigPath(c.StaticFilesPath, configDir)
 	templatesPath := getConfigPath(c.TemplatesPath, configDir)
 	enableWebAdmin := len(staticFilesPath) > 0 || len(templatesPath) > 0
-	if len(backupsPath) == 0 {
+	if backupsPath == "" {
 		return fmt.Errorf("Required directory is invalid, backup path %#v", backupsPath)
 	}
-	if enableWebAdmin && (len(staticFilesPath) == 0 || len(templatesPath) == 0) {
+	if enableWebAdmin && (staticFilesPath == "" || templatesPath == "") {
 		return fmt.Errorf("Required directory is invalid, static file path: %#v template path: %#v",
 			staticFilesPath, templatesPath)
 	}

+ 1 - 1
httpd/httpd_test.go

@@ -3430,7 +3430,7 @@ func TestRenderWebCloneUserMock(t *testing.T) {
 		assert.NoError(t, err)
 		user.FsConfig.CryptConfig.Passphrase.SetStatus(kms.SecretStatusAWS)
 		user.Password = defaultPassword
-		err = dataprovider.UpdateUser(user)
+		err = dataprovider.UpdateUser(&user)
 		assert.NoError(t, err)
 
 		req, err = http.NewRequest(http.MethodGet, webUserPath+fmt.Sprintf("?cloneFromId=%v", user.ID), nil)

+ 3 - 3
httpd/web.go

@@ -727,7 +727,7 @@ func handleWebAddUserPost(w http.ResponseWriter, r *http.Request) {
 		renderAddUserPage(w, user, err.Error())
 		return
 	}
-	err = dataprovider.AddUser(user)
+	err = dataprovider.AddUser(&user)
 	if err == nil {
 		http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
 	} else {
@@ -764,7 +764,7 @@ func handleWebUpdateUserPost(w http.ResponseWriter, r *http.Request) {
 		user.FsConfig.GCSConfig.Credentials, user.FsConfig.CryptConfig.Passphrase, user.FsConfig.SFTPConfig.Password,
 		user.FsConfig.SFTPConfig.PrivateKey)
 
-	err = dataprovider.UpdateUser(updatedUser)
+	err = dataprovider.UpdateUser(&updatedUser)
 	if err == nil {
 		if len(r.Form.Get("disconnect")) > 0 {
 			disconnectUser(user.Username)
@@ -806,7 +806,7 @@ func handleWebAddFolderPost(w http.ResponseWriter, r *http.Request) {
 	}
 	folder.MappedPath = r.Form.Get("mapped_path")
 
-	err = dataprovider.AddFolder(folder)
+	err = dataprovider.AddFolder(&folder)
 	if err == nil {
 		http.Redirect(w, r, webFoldersPath, http.StatusSeeOther)
 	} else {

+ 1 - 2
main.go

@@ -1,6 +1,5 @@
 // Fully featured and highly configurable SFTP server with optional
-// FTP/S and WebDAV support. It can serve local filesystem, S3 or
-// Google Cloud Storage.
+// FTP/S and WebDAV support.
 // For more details about features, installation, configuration and usage
 // please refer to the README inside the source tree:
 // https://github.com/drakkan/sftpgo/blob/master/README.md

+ 1 - 1
pkgs/build.sh

@@ -1,6 +1,6 @@
 #!/bin/bash
 
-NFPM_VERSION=2.2.1
+NFPM_VERSION=2.2.2
 NFPM_ARCH=${NFPM_ARCH:-amd64}
 if [ -z ${SFTPGO_VERSION} ]
 then

+ 1 - 1
service/service.go

@@ -106,7 +106,7 @@ func (s *Service) Start() error {
 
 	if s.PortableMode == 1 {
 		// create the user for portable mode
-		err = dataprovider.AddUser(s.PortableUser)
+		err = dataprovider.AddUser(&s.PortableUser)
 		if err != nil {
 			logger.ErrorToConsole("error adding portable user: %v", err)
 			return err

+ 1 - 1
service/service_portable.go

@@ -245,7 +245,7 @@ func (s *Service) configurePortableUser() string {
 	if len(s.PortableUser.Password) > 0 {
 		printablePassword = "[redacted]"
 	}
-	if len(s.PortableUser.PublicKeys) == 0 && len(s.PortableUser.Password) == 0 {
+	if len(s.PortableUser.PublicKeys) == 0 && s.PortableUser.Password == "" {
 		var b strings.Builder
 		for i := 0; i < 8; i++ {
 			b.WriteRune(chars[rand.Intn(len(chars))])

+ 1 - 1
sftpd/scp.go

@@ -668,7 +668,7 @@ func (c *scpCommand) parseUploadMessage(command string) (int64, string, error) {
 			return size, name, err
 		}
 		name = parts[2]
-		if len(name) == 0 {
+		if name == "" {
 			err = fmt.Errorf("error getting name from upload message, cannot be empty")
 			c.connection.Log(logger.LevelWarn, "error: %v", err)
 			c.sendErrorMessage(err)

+ 1 - 1
sftpd/server.go

@@ -323,7 +323,7 @@ func (c *Configuration) configureLoginBanner(serverConfig *ssh.ServerConfig, con
 }
 
 func (c *Configuration) configureKeyboardInteractiveAuth(serverConfig *ssh.ServerConfig) {
-	if len(c.KeyboardInteractiveHook) == 0 {
+	if c.KeyboardInteractiveHook == "" {
 		return
 	}
 	if !strings.HasPrefix(c.KeyboardInteractiveHook, "http") {

+ 2 - 2
sftpd/ssh_cmd.go

@@ -537,7 +537,7 @@ func (c *sshCommand) getCopyPaths() (string, string, error) {
 	if strings.HasSuffix(sshDestPath, "/") {
 		sshDestPath = path.Join(sshDestPath, path.Base(sshSourcePath))
 	}
-	if len(sshSourcePath) == 0 || len(sshDestPath) == 0 || len(c.args) != 2 {
+	if sshSourcePath == "" || sshDestPath == "" || len(c.args) != 2 {
 		err := errors.New("usage sftpgo-copy <source dir path> <destination dir path>")
 		return "", "", err
 	}
@@ -606,7 +606,7 @@ func (c *sshCommand) checkCopyPermissions(fsSourcePath, fsDestPath, sshSourcePat
 
 func (c *sshCommand) getRemovePath() (string, error) {
 	sshDestPath := c.getDestPath()
-	if len(sshDestPath) == 0 || len(c.args) != 1 {
+	if sshDestPath == "" || len(c.args) != 1 {
 		err := errors.New("usage sftpgo-remove <destination path>")
 		return "", err
 	}

+ 12 - 12
webdavd/internal_test.go

@@ -896,7 +896,7 @@ func TestBasicUsersCache(t *testing.T) {
 	}
 	u.Permissions = make(map[string][]string)
 	u.Permissions["/"] = []string{dataprovider.PermAny}
-	err := dataprovider.AddUser(u)
+	err := dataprovider.AddUser(&u)
 	assert.NoError(t, err)
 	user, err := dataprovider.UserExists(u.Username)
 	assert.NoError(t, err)
@@ -969,7 +969,7 @@ func TestBasicUsersCache(t *testing.T) {
 		assert.False(t, cachedUser.IsExpired())
 	}
 	// cache is invalidated after a user modification
-	err = dataprovider.UpdateUser(user)
+	err = dataprovider.UpdateUser(&user)
 	assert.NoError(t, err)
 	_, ok = dataprovider.GetCachedWebDAVUser(username)
 	assert.False(t, ok)
@@ -980,7 +980,7 @@ func TestBasicUsersCache(t *testing.T) {
 	_, ok = dataprovider.GetCachedWebDAVUser(username)
 	assert.True(t, ok)
 	// cache is invalidated after user deletion
-	err = dataprovider.DeleteUser(user)
+	err = dataprovider.DeleteUser(&user)
 	assert.NoError(t, err)
 	_, ok = dataprovider.GetCachedWebDAVUser(username)
 	assert.False(t, ok)
@@ -1001,25 +1001,25 @@ func TestUsersCacheSizeAndExpiration(t *testing.T) {
 	u.Password = password + "1"
 	u.Permissions = make(map[string][]string)
 	u.Permissions["/"] = []string{dataprovider.PermAny}
-	err := dataprovider.AddUser(u)
+	err := dataprovider.AddUser(&u)
 	assert.NoError(t, err)
 	user1, err := dataprovider.UserExists(u.Username)
 	assert.NoError(t, err)
 	u.Username = username + "2"
 	u.Password = password + "2"
-	err = dataprovider.AddUser(u)
+	err = dataprovider.AddUser(&u)
 	assert.NoError(t, err)
 	user2, err := dataprovider.UserExists(u.Username)
 	assert.NoError(t, err)
 	u.Username = username + "3"
 	u.Password = password + "3"
-	err = dataprovider.AddUser(u)
+	err = dataprovider.AddUser(&u)
 	assert.NoError(t, err)
 	user3, err := dataprovider.UserExists(u.Username)
 	assert.NoError(t, err)
 	u.Username = username + "4"
 	u.Password = password + "4"
-	err = dataprovider.AddUser(u)
+	err = dataprovider.AddUser(&u)
 	assert.NoError(t, err)
 	user4, err := dataprovider.UserExists(u.Username)
 	assert.NoError(t, err)
@@ -1137,7 +1137,7 @@ func TestUsersCacheSizeAndExpiration(t *testing.T) {
 	assert.True(t, ok)
 
 	// now remove user1 after an update
-	err = dataprovider.UpdateUser(user1)
+	err = dataprovider.UpdateUser(&user1)
 	assert.NoError(t, err)
 	_, ok = dataprovider.GetCachedWebDAVUser(user1.Username)
 	assert.False(t, ok)
@@ -1164,13 +1164,13 @@ func TestUsersCacheSizeAndExpiration(t *testing.T) {
 	_, ok = dataprovider.GetCachedWebDAVUser(user4.Username)
 	assert.True(t, ok)
 
-	err = dataprovider.DeleteUser(user1)
+	err = dataprovider.DeleteUser(&user1)
 	assert.NoError(t, err)
-	err = dataprovider.DeleteUser(user2)
+	err = dataprovider.DeleteUser(&user2)
 	assert.NoError(t, err)
-	err = dataprovider.DeleteUser(user3)
+	err = dataprovider.DeleteUser(&user3)
 	assert.NoError(t, err)
-	err = dataprovider.DeleteUser(user4)
+	err = dataprovider.DeleteUser(&user4)
 	assert.NoError(t, err)
 
 	err = os.RemoveAll(u.GetHomeDir())

+ 1 - 1
webdavd/server.go

@@ -207,7 +207,7 @@ func (s *webDavServer) authenticate(r *http.Request, ip string) (dataprovider.Us
 		if cachedUser.IsExpired() {
 			dataprovider.RemoveCachedWebDAVUser(username)
 		} else {
-			if len(password) > 0 && cachedUser.Password == password {
+			if password != "" && cachedUser.Password == password {
 				return cachedUser.User, true, cachedUser.LockSystem, nil
 			}
 			updateLoginMetrics(username, ip, dataprovider.ErrInvalidCredentials)