shared mode: ensure to clear webdav cache for deleted users

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino 2022-07-17 18:48:41 +02:00
parent 55b47cf741
commit fd52475ae2
No known key found for this signature in database
GPG key ID: 2F1FB59433D5A8CB
12 changed files with 69 additions and 28 deletions

View file

@ -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 {

View file

@ -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)
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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)
}

View file

@ -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())

View file

@ -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 {

View file

@ -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])
}

View file

@ -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

View file

@ -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{

View file

@ -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)