Browse Source

shared mode: ensure to clear webdav cache for deleted users

Signed-off-by: Nicola Murino <[email protected]>
Nicola Murino 3 years ago
parent
commit
fd52475ae2

+ 1 - 1
dataprovider/bolt.go

@@ -626,7 +626,7 @@ func (p *BoltProvider) updateUser(user *User) error {
 	})
 }
 
-func (p *BoltProvider) deleteUser(user User) error {
+func (p *BoltProvider) deleteUser(user User, softDelete bool) error {
 	return p.dbHandle.Update(func(tx *bolt.Tx) error {
 		bucket, err := p.getUsersBucket(tx)
 		if err != nil {

+ 4 - 4
dataprovider/dataprovider.go

@@ -632,7 +632,7 @@ type Provider interface {
 	userExists(username string) (User, error)
 	addUser(user *User) error
 	updateUser(user *User) error
-	deleteUser(user User) error
+	deleteUser(user User, softDelete bool) error
 	updateUserPassword(username, password string) error
 	getUsers(limit int, offset int, order string) ([]User, error)
 	dumpUsers() ([]User, error)
@@ -1577,7 +1577,7 @@ func DeleteEventRule(name string, executor, ipAddress string) error {
 	if err != nil {
 		return err
 	}
-	err = provider.deleteEventRule(rule, config.GetShared() == 1)
+	err = provider.deleteEventRule(rule, config.IsShared == 1)
 	if err == nil {
 		EventManager.RemoveRule(rule.Name)
 		executeAction(operationDelete, executor, ipAddress, actionObjectEventRule, rule.Name, &rule)
@@ -1716,10 +1716,10 @@ func DeleteUser(username, executor, ipAddress string) error {
 	if err != nil {
 		return err
 	}
-	err = provider.deleteUser(user)
+	err = provider.deleteUser(user, config.IsShared == 1)
 	if err == nil {
 		RemoveCachedWebDAVUser(user.Username)
-		delayedQuotaUpdater.resetUserQuota(username)
+		delayedQuotaUpdater.resetUserQuota(user.Username)
 		cachedPasswords.Remove(username)
 		executeAction(operationDelete, executor, ipAddress, actionObjectUser, user.Username, &user)
 	}

+ 1 - 1
dataprovider/memory.go

@@ -377,7 +377,7 @@ func (p *MemoryProvider) updateUser(user *User) error {
 	return nil
 }
 
-func (p *MemoryProvider) deleteUser(user User) error {
+func (p *MemoryProvider) deleteUser(user User, softDelete bool) error {
 	p.dbHandle.Lock()
 	defer p.dbHandle.Unlock()
 	if p.dbHandle.isClosed {

+ 2 - 2
dataprovider/mysql.go

@@ -292,8 +292,8 @@ func (p *MySQLProvider) updateUser(user *User) error {
 	return sqlCommonUpdateUser(user, p.dbHandle)
 }
 
-func (p *MySQLProvider) deleteUser(user User) error {
-	return sqlCommonDeleteUser(user, p.dbHandle)
+func (p *MySQLProvider) deleteUser(user User, softDelete bool) error {
+	return sqlCommonDeleteUser(user, softDelete, p.dbHandle)
 }
 
 func (p *MySQLProvider) updateUserPassword(username, password string) error {

+ 2 - 2
dataprovider/pgsql.go

@@ -267,8 +267,8 @@ func (p *PGSQLProvider) updateUser(user *User) error {
 	return sqlCommonUpdateUser(user, p.dbHandle)
 }
 
-func (p *PGSQLProvider) deleteUser(user User) error {
-	return sqlCommonDeleteUser(user, p.dbHandle)
+func (p *PGSQLProvider) deleteUser(user User, softDelete bool) error {
+	return sqlCommonDeleteUser(user, softDelete, p.dbHandle)
 }
 
 func (p *PGSQLProvider) updateUserPassword(username, password string) error {

+ 12 - 2
dataprovider/scheduler.go

@@ -71,8 +71,18 @@ func checkCacheUpdates() {
 		return
 	}
 	for _, user := range users {
-		providerLog(logger.LevelDebug, "invalidate caches for user %#v", user.Username)
-		webDAVUsersCache.swap(&user)
+		providerLog(logger.LevelDebug, "invalidate caches for user %q", user.Username)
+		if user.DeletedAt > 0 {
+			deletedAt := util.GetTimeFromMsecSinceEpoch(user.DeletedAt)
+			if deletedAt.Add(30 * time.Minute).Before(time.Now()) {
+				providerLog(logger.LevelDebug, "removing user %q deleted at %s", user.Username, deletedAt)
+				go provider.deleteUser(user, false) //nolint:errcheck
+			}
+			webDAVUsersCache.remove(user.Username)
+			delayedQuotaUpdater.resetUserQuota(user.Username)
+		} else {
+			webDAVUsersCache.swap(&user)
+		}
 		cachedPasswords.Remove(user.Username)
 	}
 

+ 19 - 3
dataprovider/sqlcommon.go

@@ -989,11 +989,27 @@ func sqlCommonUpdateUser(user *User, dbHandle *sql.DB) error {
 	})
 }
 
-func sqlCommonDeleteUser(user User, dbHandle *sql.DB) error {
+func sqlCommonDeleteUser(user User, softDelete bool, dbHandle *sql.DB) error {
 	ctx, cancel := context.WithTimeout(context.Background(), defaultSQLQueryTimeout)
 	defer cancel()
 
-	q := getDeleteUserQuery()
+	q := getDeleteUserQuery(softDelete)
+	if softDelete {
+		return sqlCommonExecuteTx(ctx, dbHandle, func(tx *sql.Tx) error {
+			if err := sqlCommonClearUserFolderMapping(ctx, &user, tx); err != nil {
+				return err
+			}
+			if err := sqlCommonClearUserGroupMapping(ctx, &user, tx); err != nil {
+				return err
+			}
+			ts := util.GetTimeAsMsSinceEpoch(time.Now())
+			res, err := tx.ExecContext(ctx, q, ts, ts, user.Username)
+			if err != nil {
+				return err
+			}
+			return sqlCommonRequireRowAffected(res)
+		})
+	}
 	res, err := dbHandle.ExecContext(ctx, q, user.ID)
 	if err != nil {
 		return err
@@ -1703,7 +1719,7 @@ func getUserFromDbRow(row sqlScanner) (User, error) {
 		&user.QuotaSize, &user.QuotaFiles, &permissions, &user.UsedQuotaSize, &user.UsedQuotaFiles, &user.LastQuotaUpdate,
 		&user.UploadBandwidth, &user.DownloadBandwidth, &user.ExpirationDate, &user.LastLogin, &user.Status, &filters, &fsConfig,
 		&additionalInfo, &description, &email, &user.CreatedAt, &user.UpdatedAt, &user.UploadDataTransfer, &user.DownloadDataTransfer,
-		&user.TotalDataTransfer, &user.UsedUploadDataTransfer, &user.UsedDownloadDataTransfer)
+		&user.TotalDataTransfer, &user.UsedUploadDataTransfer, &user.UsedDownloadDataTransfer, &user.DeletedAt)
 	if err != nil {
 		if errors.Is(err, sql.ErrNoRows) {
 			return user, util.NewRecordNotFoundError(err.Error())

+ 2 - 2
dataprovider/sqlite.go

@@ -239,8 +239,8 @@ func (p *SQLiteProvider) updateUser(user *User) error {
 	return sqlCommonUpdateUser(user, p.dbHandle)
 }
 
-func (p *SQLiteProvider) deleteUser(user User) error {
-	return sqlCommonDeleteUser(user, p.dbHandle)
+func (p *SQLiteProvider) deleteUser(user User, softDelete bool) error {
+	return sqlCommonDeleteUser(user, softDelete, p.dbHandle)
 }
 
 func (p *SQLiteProvider) updateUserPassword(username, password string) error {

+ 13 - 7
dataprovider/sqlqueries.go

@@ -12,7 +12,7 @@ const (
 	selectUserFields = "id,username,password,public_keys,home_dir,uid,gid,max_sessions,quota_size,quota_files,permissions,used_quota_size," +
 		"used_quota_files,last_quota_update,upload_bandwidth,download_bandwidth,expiration_date,last_login,status,filters,filesystem," +
 		"additional_info,description,email,created_at,updated_at,upload_data_transfer,download_data_transfer,total_data_transfer," +
-		"used_upload_data_transfer,used_download_data_transfer"
+		"used_upload_data_transfer,used_download_data_transfer,deleted_at"
 	selectFolderFields = "id,path,used_quota_size,used_quota_files,last_quota_update,name,description,filesystem"
 	selectAdminFields  = "id,username,password,status,email,permissions,filters,additional_info,description,created_at,updated_at,last_login"
 	selectAPIKeyFields = "key_id,name,api_key,scope,created_at,updated_at,last_use_at,expires_at,description,user_id,admin_id"
@@ -380,12 +380,13 @@ func getRelatedAdminsForAPIKeysQuery(apiKeys []APIKey) string {
 }
 
 func getUserByUsernameQuery() string {
-	return fmt.Sprintf(`SELECT %s FROM %s WHERE username = %s`, selectUserFields, sqlTableUsers, sqlPlaceholders[0])
+	return fmt.Sprintf(`SELECT %s FROM %s WHERE username = %s AND deleted_at = 0`,
+		selectUserFields, sqlTableUsers, sqlPlaceholders[0])
 }
 
 func getUsersQuery(order string) string {
-	return fmt.Sprintf(`SELECT %s FROM %s ORDER BY username %s LIMIT %s OFFSET %s`, selectUserFields, sqlTableUsers,
-		order, sqlPlaceholders[0], sqlPlaceholders[1])
+	return fmt.Sprintf(`SELECT %s FROM %s WHERE deleted_at = 0 ORDER BY username %s LIMIT %s OFFSET %s`,
+		selectUserFields, sqlTableUsers, order, sqlPlaceholders[0], sqlPlaceholders[1])
 }
 
 func getUsersForQuotaCheckQuery(numArgs int) string {
@@ -407,11 +408,12 @@ func getUsersForQuotaCheckQuery(numArgs int) string {
 }
 
 func getRecentlyUpdatedUsersQuery() string {
-	return fmt.Sprintf(`SELECT %s FROM %s WHERE updated_at >= %s`, selectUserFields, sqlTableUsers, sqlPlaceholders[0])
+	return fmt.Sprintf(`SELECT %s FROM %s WHERE updated_at >= %s OR deleted_at > 0`,
+		selectUserFields, sqlTableUsers, sqlPlaceholders[0])
 }
 
 func getDumpUsersQuery() string {
-	return fmt.Sprintf(`SELECT %s FROM %s`, selectUserFields, sqlTableUsers)
+	return fmt.Sprintf(`SELECT %s FROM %s WHERE deleted_at = 0`, selectUserFields, sqlTableUsers)
 }
 
 func getDumpFoldersQuery() string {
@@ -493,7 +495,11 @@ func getUpdateUserPasswordQuery() string {
 	return fmt.Sprintf(`UPDATE %s SET password=%s WHERE username = %s`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1])
 }
 
-func getDeleteUserQuery() string {
+func getDeleteUserQuery(softDelete bool) string {
+	if softDelete {
+		return fmt.Sprintf(`UPDATE %s SET updated_at=%s,deleted_at=%s WHERE username = %s`,
+			sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2])
+	}
 	return fmt.Sprintf(`DELETE FROM %s WHERE id = %s`, sqlTableUsers, sqlPlaceholders[0])
 }
 

+ 2 - 0
dataprovider/user.go

@@ -128,6 +128,8 @@ type User struct {
 	fsCache map[string]vfs.Fs `json:"-"`
 	// true if group settings are already applied for this user
 	groupSettingsApplied bool `json:"-"`
+	// in multi node setups we mark the user as deleted to be able to update the webdav cache
+	DeletedAt int64 `json:"-"`
 }
 
 // GetFilesystem returns the base filesystem for this user

+ 7 - 2
httpd/webadmin.go

@@ -790,8 +790,13 @@ func (s *httpdServer) renderUserPage(w http.ResponseWriter, r *http.Request, use
 		title = "User template"
 		currentURL = webTemplateUser
 	}
-	if user.Password != "" && user.IsPasswordHashed() && mode == userPageModeUpdate {
-		user.Password = redactedSecret
+	if user.Password != "" && user.IsPasswordHashed() {
+		switch mode {
+		case userPageModeUpdate:
+			user.Password = redactedSecret
+		default:
+			user.Password = ""
+		}
 	}
 	user.FsConfig.RedactedSecret = redactedSecret
 	data := userPage{

+ 4 - 2
sftpd/sftpd_test.go

@@ -1235,8 +1235,10 @@ func TestRealPath(t *testing.T) {
 			assert.ErrorIs(t, err, os.ErrPermission)
 			err = os.Remove(filepath.Join(localUser.GetHomeDir(), subdir, "temp"))
 			assert.NoError(t, err)
-			err = os.RemoveAll(filepath.Join(localUser.GetHomeDir(), subdir))
-			assert.NoError(t, err)
+			if user.Username == localUser.Username {
+				err = os.RemoveAll(filepath.Join(localUser.GetHomeDir(), subdir))
+				assert.NoError(t, err)
+			}
 		}
 	}
 	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)