mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-22 07:30:25 +00:00
dataprovider: add timestamp fields for users and admins
This commit is contained in:
parent
b99d4ce82e
commit
be3857d572
52 changed files with 725 additions and 76 deletions
|
@ -1,3 +1,4 @@
|
|||
//go:build !noportable
|
||||
// +build !noportable
|
||||
|
||||
package cmd
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build noportable
|
||||
// +build noportable
|
||||
|
||||
package cmd
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package config
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package config
|
||||
|
|
|
@ -68,6 +68,12 @@ type Admin struct {
|
|||
Filters AdminFilters `json:"filters,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
AdditionalInfo string `json:"additional_info,omitempty"`
|
||||
// Creation time as unix timestamp in milliseconds. It will be 0 for admins created before v2.2.0
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
// last update time as unix timestamp in milliseconds
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
// Last login as unix timestamp in milliseconds
|
||||
LastLogin int64 `json:"last_login"`
|
||||
}
|
||||
|
||||
func (a *Admin) checkPassword() error {
|
||||
|
@ -260,6 +266,9 @@ func (a *Admin) getACopy() Admin {
|
|||
Filters: filters,
|
||||
AdditionalInfo: a.AdditionalInfo,
|
||||
Description: a.Description,
|
||||
LastLogin: a.LastLogin,
|
||||
CreatedAt: a.CreatedAt,
|
||||
UpdatedAt: a.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -125,9 +125,6 @@ func (k *APIKey) validate() error {
|
|||
if err := k.checkKey(); err != nil {
|
||||
return err
|
||||
}
|
||||
if k.CreatedAt == 0 {
|
||||
k.CreatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
|
||||
}
|
||||
if k.User != "" && k.Admin != "" {
|
||||
return util.NewValidationError("an API key can be related to a user or an admin, not both")
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !nobolt
|
||||
// +build !nobolt
|
||||
|
||||
package dataprovider
|
||||
|
@ -19,7 +20,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
boltDatabaseVersion = 11
|
||||
boltDatabaseVersion = 12
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -191,6 +192,36 @@ func (p *BoltProvider) updateAPIKeyLastUse(keyID string) error {
|
|||
})
|
||||
}
|
||||
|
||||
func (p *BoltProvider) setUpdatedAt(username string) {
|
||||
p.dbHandle.Update(func(tx *bolt.Tx) error { //nolint:errcheck
|
||||
bucket, err := getUsersBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var u []byte
|
||||
if u = bucket.Get([]byte(username)); u == nil {
|
||||
return util.NewRecordNotFoundError(fmt.Sprintf("username %#v does not exist, unable to update updated at", username))
|
||||
}
|
||||
var user User
|
||||
err = json.Unmarshal(u, &user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user.UpdatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
|
||||
buf, err := json.Marshal(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = bucket.Put([]byte(username), buf)
|
||||
if err == nil {
|
||||
providerLog(logger.LevelDebug, "updated at set for user %#v", username)
|
||||
} else {
|
||||
providerLog(logger.LevelWarn, "error setting updated_at for user %#v: %v", username, err)
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (p *BoltProvider) updateLastLogin(username string) error {
|
||||
return p.dbHandle.Update(func(tx *bolt.Tx) error {
|
||||
bucket, err := getUsersBucket(tx)
|
||||
|
@ -221,6 +252,36 @@ func (p *BoltProvider) updateLastLogin(username string) error {
|
|||
})
|
||||
}
|
||||
|
||||
func (p *BoltProvider) updateAdminLastLogin(username string) error {
|
||||
return p.dbHandle.Update(func(tx *bolt.Tx) error {
|
||||
bucket, err := getAdminsBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var a []byte
|
||||
if a = bucket.Get([]byte(username)); a == nil {
|
||||
return util.NewRecordNotFoundError(fmt.Sprintf("admin %#v does not exist, unable to update last login", username))
|
||||
}
|
||||
var admin Admin
|
||||
err = json.Unmarshal(a, &admin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
admin.LastLogin = util.GetTimeAsMsSinceEpoch(time.Now())
|
||||
buf, err := json.Marshal(admin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = bucket.Put([]byte(username), buf)
|
||||
if err == nil {
|
||||
providerLog(logger.LevelDebug, "last login updated for admin %#v", username)
|
||||
return err
|
||||
}
|
||||
providerLog(logger.LevelWarn, "error updating last login for admin %#v: %v", username, err)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (p *BoltProvider) updateQuota(username string, filesAdd int, sizeAdd int64, reset bool) error {
|
||||
return p.dbHandle.Update(func(tx *bolt.Tx) error {
|
||||
bucket, err := getUsersBucket(tx)
|
||||
|
@ -300,6 +361,9 @@ func (p *BoltProvider) addAdmin(admin *Admin) error {
|
|||
return err
|
||||
}
|
||||
admin.ID = int64(id)
|
||||
admin.LastLogin = 0
|
||||
admin.CreatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
|
||||
admin.UpdatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
|
||||
buf, err := json.Marshal(admin)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -330,6 +394,9 @@ func (p *BoltProvider) updateAdmin(admin *Admin) error {
|
|||
}
|
||||
|
||||
admin.ID = oldAdmin.ID
|
||||
admin.CreatedAt = oldAdmin.CreatedAt
|
||||
admin.LastLogin = oldAdmin.LastLogin
|
||||
admin.UpdatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
|
||||
buf, err := json.Marshal(admin)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -478,6 +545,8 @@ func (p *BoltProvider) addUser(user *User) error {
|
|||
user.UsedQuotaSize = 0
|
||||
user.UsedQuotaFiles = 0
|
||||
user.LastLogin = 0
|
||||
user.CreatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
|
||||
user.UpdatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
|
||||
for idx := range user.VirtualFolders {
|
||||
err = addUserToFolderMapping(&user.VirtualFolders[idx].BaseVirtualFolder, user, folderBucket)
|
||||
if err != nil {
|
||||
|
@ -532,6 +601,8 @@ func (p *BoltProvider) updateUser(user *User) error {
|
|||
user.UsedQuotaSize = oldUser.UsedQuotaSize
|
||||
user.UsedQuotaFiles = oldUser.UsedQuotaFiles
|
||||
user.LastLogin = oldUser.LastLogin
|
||||
user.CreatedAt = oldUser.CreatedAt
|
||||
user.UpdatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
|
||||
buf, err := json.Marshal(user)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -916,7 +987,9 @@ func (p *BoltProvider) addAPIKey(apiKey *APIKey) error {
|
|||
return err
|
||||
}
|
||||
apiKey.ID = int64(id)
|
||||
apiKey.CreatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
|
||||
apiKey.UpdatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
|
||||
apiKey.LastUseAt = 0
|
||||
buf, err := json.Marshal(apiKey)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1077,7 +1150,9 @@ func (p *BoltProvider) migrateDatabase() error {
|
|||
logger.ErrorToConsole("%v", err)
|
||||
return err
|
||||
case version == 10:
|
||||
return updateBoltDatabaseVersion(p.dbHandle, 11)
|
||||
return updateBoltDatabaseVersion(p.dbHandle, 12)
|
||||
case version == 11:
|
||||
return updateBoltDatabaseVersion(p.dbHandle, 12)
|
||||
default:
|
||||
if version > boltDatabaseVersion {
|
||||
providerLog(logger.LevelWarn, "database version %v is newer than the supported one: %v", version,
|
||||
|
@ -1099,6 +1174,8 @@ func (p *BoltProvider) revertDatabase(targetVersion int) error {
|
|||
return errors.New("current version match target version, nothing to do")
|
||||
}
|
||||
switch dbVersion.Version {
|
||||
case 12:
|
||||
return updateBoltDatabaseVersion(p.dbHandle, 10)
|
||||
case 11:
|
||||
return updateBoltDatabaseVersion(p.dbHandle, 10)
|
||||
default:
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build nobolt
|
||||
// +build nobolt
|
||||
|
||||
package dataprovider
|
||||
|
|
|
@ -392,6 +392,8 @@ type Provider interface {
|
|||
getUsers(limit int, offset int, order string) ([]User, error)
|
||||
dumpUsers() ([]User, error)
|
||||
updateLastLogin(username string) error
|
||||
updateAdminLastLogin(username string) error
|
||||
setUpdatedAt(username string)
|
||||
getFolders(limit, offset int, order string) ([]vfs.BaseVirtualFolder, error)
|
||||
getFolderByName(name string) (vfs.BaseVirtualFolder, error)
|
||||
addFolder(folder *vfs.BaseVirtualFolder) error
|
||||
|
@ -813,7 +815,7 @@ func UpdateAPIKeyLastUse(apiKey *APIKey) error {
|
|||
}
|
||||
|
||||
// UpdateLastLogin updates the last login field for the given SFTPGo user
|
||||
func UpdateLastLogin(user *User) error {
|
||||
func UpdateLastLogin(user *User) {
|
||||
lastLogin := util.GetTimeFromMsecSinceEpoch(user.LastLogin)
|
||||
diff := -time.Until(lastLogin)
|
||||
if diff < 0 || diff > lastLoginMinDelay {
|
||||
|
@ -821,9 +823,16 @@ func UpdateLastLogin(user *User) error {
|
|||
if err == nil {
|
||||
webDAVUsersCache.updateLastLogin(user.Username)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateAdminLastLogin updates the last login field for the given SFTPGo admin
|
||||
func UpdateAdminLastLogin(admin *Admin) {
|
||||
lastLogin := util.GetTimeFromMsecSinceEpoch(admin.LastLogin)
|
||||
diff := -time.Until(lastLogin)
|
||||
if diff < 0 || diff > lastLoginMinDelay {
|
||||
provider.updateAdminLastLogin(admin.Username) //nolint:errcheck
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateUserQuota updates the quota for the given SFTP user adding filesAdd and sizeAdd.
|
||||
|
@ -1026,9 +1035,16 @@ func UpdateFolder(folder *vfs.BaseVirtualFolder, users []string) error {
|
|||
err := provider.updateFolder(folder)
|
||||
if err == nil {
|
||||
for _, user := range users {
|
||||
provider.setUpdatedAt(user)
|
||||
u, err := provider.userExists(user)
|
||||
if err == nil {
|
||||
webDAVUsersCache.swap(&u)
|
||||
executeAction(operationUpdate, &u)
|
||||
} else {
|
||||
RemoveCachedWebDAVUser(user)
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -1041,6 +1057,7 @@ func DeleteFolder(folderName string) error {
|
|||
err = provider.deleteFolder(&folder)
|
||||
if err == nil {
|
||||
for _, user := range folder.Users {
|
||||
provider.setUpdatedAt(user)
|
||||
RemoveCachedWebDAVUser(user)
|
||||
}
|
||||
delayedQuotaUpdater.resetFolderQuota(folderName)
|
||||
|
@ -2252,6 +2269,7 @@ func executePreLoginHook(username, loginMethod, ip, protocol string) (User, erro
|
|||
userUsedQuotaFiles := u.UsedQuotaFiles
|
||||
userLastQuotaUpdate := u.LastQuotaUpdate
|
||||
userLastLogin := u.LastLogin
|
||||
userCreatedAt := u.CreatedAt
|
||||
err = json.Unmarshal(out, &u)
|
||||
if err != nil {
|
||||
return u, fmt.Errorf("invalid pre-login hook response %#v, error: %v", string(out), err)
|
||||
|
@ -2261,9 +2279,11 @@ func executePreLoginHook(username, loginMethod, ip, protocol string) (User, erro
|
|||
u.UsedQuotaFiles = userUsedQuotaFiles
|
||||
u.LastQuotaUpdate = userLastQuotaUpdate
|
||||
u.LastLogin = userLastLogin
|
||||
u.CreatedAt = userCreatedAt
|
||||
if userID == 0 {
|
||||
err = provider.addUser(&u)
|
||||
} else {
|
||||
u.UpdatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
|
||||
err = provider.updateUser(&u)
|
||||
if err == nil {
|
||||
webDAVUsersCache.swap(&u)
|
||||
|
@ -2464,6 +2484,8 @@ func doExternalAuth(username, password string, pubKey []byte, keyboardInteractiv
|
|||
user.UsedQuotaFiles = u.UsedQuotaFiles
|
||||
user.LastQuotaUpdate = u.LastQuotaUpdate
|
||||
user.LastLogin = u.LastLogin
|
||||
user.CreatedAt = u.CreatedAt
|
||||
user.UpdatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
|
||||
err = provider.updateUser(&user)
|
||||
if err == nil {
|
||||
webDAVUsersCache.swap(&user)
|
||||
|
|
|
@ -158,6 +158,20 @@ func (p *MemoryProvider) updateAPIKeyLastUse(keyID string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *MemoryProvider) setUpdatedAt(username string) {
|
||||
p.dbHandle.Lock()
|
||||
defer p.dbHandle.Unlock()
|
||||
if p.dbHandle.isClosed {
|
||||
return
|
||||
}
|
||||
user, err := p.userExistsInternal(username)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
user.UpdatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
|
||||
p.dbHandle.users[user.Username] = user
|
||||
}
|
||||
|
||||
func (p *MemoryProvider) updateLastLogin(username string) error {
|
||||
p.dbHandle.Lock()
|
||||
defer p.dbHandle.Unlock()
|
||||
|
@ -173,6 +187,21 @@ func (p *MemoryProvider) updateLastLogin(username string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *MemoryProvider) updateAdminLastLogin(username string) error {
|
||||
p.dbHandle.Lock()
|
||||
defer p.dbHandle.Unlock()
|
||||
if p.dbHandle.isClosed {
|
||||
return errMemoryProviderClosed
|
||||
}
|
||||
admin, err := p.adminExistsInternal(username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
admin.LastLogin = util.GetTimeAsMsSinceEpoch(time.Now())
|
||||
p.dbHandle.admins[admin.Username] = admin
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *MemoryProvider) updateQuota(username string, filesAdd int, sizeAdd int64, reset bool) error {
|
||||
p.dbHandle.Lock()
|
||||
defer p.dbHandle.Unlock()
|
||||
|
@ -235,6 +264,8 @@ func (p *MemoryProvider) addUser(user *User) error {
|
|||
user.UsedQuotaSize = 0
|
||||
user.UsedQuotaFiles = 0
|
||||
user.LastLogin = 0
|
||||
user.CreatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
|
||||
user.UpdatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
|
||||
user.VirtualFolders = p.joinVirtualFoldersFields(user)
|
||||
p.dbHandle.users[user.Username] = user.getACopy()
|
||||
p.dbHandle.usernames = append(p.dbHandle.usernames, user.Username)
|
||||
|
@ -268,6 +299,8 @@ func (p *MemoryProvider) updateUser(user *User) error {
|
|||
user.UsedQuotaSize = u.UsedQuotaSize
|
||||
user.UsedQuotaFiles = u.UsedQuotaFiles
|
||||
user.LastLogin = u.LastLogin
|
||||
user.CreatedAt = u.CreatedAt
|
||||
user.UpdatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
|
||||
user.ID = u.ID
|
||||
// pre-login and external auth hook will use the passed *user so save a copy
|
||||
p.dbHandle.users[user.Username] = user.getACopy()
|
||||
|
@ -407,6 +440,9 @@ func (p *MemoryProvider) addAdmin(admin *Admin) error {
|
|||
return fmt.Errorf("admin %#v already exists", admin.Username)
|
||||
}
|
||||
admin.ID = p.getNextAdminID()
|
||||
admin.CreatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
|
||||
admin.UpdatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
|
||||
admin.LastLogin = 0
|
||||
p.dbHandle.admins[admin.Username] = admin.getACopy()
|
||||
p.dbHandle.adminsUsernames = append(p.dbHandle.adminsUsernames, admin.Username)
|
||||
sort.Strings(p.dbHandle.adminsUsernames)
|
||||
|
@ -428,6 +464,9 @@ func (p *MemoryProvider) updateAdmin(admin *Admin) error {
|
|||
return err
|
||||
}
|
||||
admin.ID = a.ID
|
||||
admin.CreatedAt = a.CreatedAt
|
||||
admin.LastLogin = a.LastLogin
|
||||
admin.UpdatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
|
||||
p.dbHandle.admins[admin.Username] = admin.getACopy()
|
||||
return nil
|
||||
}
|
||||
|
@ -825,7 +864,9 @@ func (p *MemoryProvider) addAPIKey(apiKey *APIKey) error {
|
|||
if err == nil {
|
||||
return fmt.Errorf("API key %#v already exists", apiKey.KeyID)
|
||||
}
|
||||
apiKey.CreatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
|
||||
apiKey.UpdatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
|
||||
apiKey.LastUseAt = 0
|
||||
p.dbHandle.apiKeys[apiKey.KeyID] = apiKey.getACopy()
|
||||
p.dbHandle.apiKeysIDs = append(p.dbHandle.apiKeysIDs, apiKey.KeyID)
|
||||
sort.Strings(p.dbHandle.apiKeysIDs)
|
||||
|
@ -1041,23 +1082,52 @@ func (p *MemoryProvider) reloadConfig() error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := p.restoreAPIKeys(&dump); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
providerLog(logger.LevelDebug, "config loaded from file: %#v", p.dbHandle.configFile)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *MemoryProvider) restoreAPIKeys(dump *BackupData) error {
|
||||
for _, apiKey := range dump.APIKeys {
|
||||
if apiKey.KeyID == "" {
|
||||
return fmt.Errorf("cannot restore an empty API key: %+v", apiKey)
|
||||
}
|
||||
k, err := p.apiKeyExists(apiKey.KeyID)
|
||||
apiKey := apiKey // pin
|
||||
if err == nil {
|
||||
apiKey.ID = k.ID
|
||||
err = UpdateAPIKey(&apiKey)
|
||||
if err != nil {
|
||||
providerLog(logger.LevelWarn, "error updating API key %#v: %v", apiKey.KeyID, err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = AddAPIKey(&apiKey)
|
||||
if err != nil {
|
||||
providerLog(logger.LevelWarn, "error adding API key %#v: %v", apiKey.KeyID, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *MemoryProvider) restoreAdmins(dump *BackupData) error {
|
||||
for _, admin := range dump.Admins {
|
||||
a, err := p.adminExists(admin.Username)
|
||||
admin := admin // pin
|
||||
if err == nil {
|
||||
admin.ID = a.ID
|
||||
err = p.updateAdmin(&admin)
|
||||
err = UpdateAdmin(&admin)
|
||||
if err != nil {
|
||||
providerLog(logger.LevelWarn, "error updating admin %#v: %v", admin.Username, err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = p.addAdmin(&admin)
|
||||
err = AddAdmin(&admin)
|
||||
if err != nil {
|
||||
providerLog(logger.LevelWarn, "error adding admin %#v: %v", admin.Username, err)
|
||||
return err
|
||||
|
@ -1073,14 +1143,14 @@ func (p *MemoryProvider) restoreFolders(dump *BackupData) error {
|
|||
f, err := p.getFolderByName(folder.Name)
|
||||
if err == nil {
|
||||
folder.ID = f.ID
|
||||
err = p.updateFolder(&folder)
|
||||
err = UpdateFolder(&folder, f.Users)
|
||||
if err != nil {
|
||||
providerLog(logger.LevelWarn, "error updating folder %#v: %v", folder.Name, err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
folder.Users = nil
|
||||
err = p.addFolder(&folder)
|
||||
err = AddFolder(&folder)
|
||||
if err != nil {
|
||||
providerLog(logger.LevelWarn, "error adding folder %#v: %v", folder.Name, err)
|
||||
return err
|
||||
|
@ -1096,13 +1166,13 @@ func (p *MemoryProvider) restoreUsers(dump *BackupData) error {
|
|||
u, err := p.userExists(user.Username)
|
||||
if err == nil {
|
||||
user.ID = u.ID
|
||||
err = p.updateUser(&user)
|
||||
err = UpdateUser(&user)
|
||||
if err != nil {
|
||||
providerLog(logger.LevelWarn, "error updating user %#v: %v", user.Username, err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = p.addUser(&user)
|
||||
err = AddUser(&user)
|
||||
if err != nil {
|
||||
providerLog(logger.LevelWarn, "error adding user %#v: %v", user.Username, err)
|
||||
return err
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !nomysql
|
||||
// +build !nomysql
|
||||
|
||||
package dataprovider
|
||||
|
@ -46,6 +47,22 @@ const (
|
|||
"ALTER TABLE `{{api_keys}}` ADD CONSTRAINT `{{prefix}}api_keys_admin_id_fk_admins_id` FOREIGN KEY (`admin_id`) REFERENCES `{{admins}}` (`id`) ON DELETE CASCADE;" +
|
||||
"ALTER TABLE `{{api_keys}}` ADD CONSTRAINT `{{prefix}}api_keys_user_id_fk_users_id` FOREIGN KEY (`user_id`) REFERENCES `{{users}}` (`id`) ON DELETE CASCADE;"
|
||||
mysqlV11DownSQL = "DROP TABLE `{{api_keys}}` CASCADE;"
|
||||
mysqlV12SQL = "ALTER TABLE `{{admins}}` ADD COLUMN `created_at` bigint DEFAULT 0 NOT NULL;" +
|
||||
"ALTER TABLE `{{admins}}` ALTER COLUMN `created_at` DROP DEFAULT;" +
|
||||
"ALTER TABLE `{{admins}}` ADD COLUMN `updated_at` bigint DEFAULT 0 NOT NULL;" +
|
||||
"ALTER TABLE `{{admins}}` ALTER COLUMN `updated_at` DROP DEFAULT;" +
|
||||
"ALTER TABLE `{{admins}}` ADD COLUMN `last_login` bigint DEFAULT 0 NOT NULL;" +
|
||||
"ALTER TABLE `{{admins}}` ALTER COLUMN `last_login` DROP DEFAULT;" +
|
||||
"ALTER TABLE `{{users}}` ADD COLUMN `created_at` bigint DEFAULT 0 NOT NULL;" +
|
||||
"ALTER TABLE `{{users}}` ALTER COLUMN `created_at` DROP DEFAULT;" +
|
||||
"ALTER TABLE `{{users}}` ADD COLUMN `updated_at` bigint DEFAULT 0 NOT NULL;" +
|
||||
"ALTER TABLE `{{users}}` ALTER COLUMN `updated_at` DROP DEFAULT;" +
|
||||
"CREATE INDEX `{{prefix}}users_updated_at_idx` ON `{{users}}` (`updated_at`);"
|
||||
mysqlV12DownSQL = "ALTER TABLE `{{admins}}` DROP COLUMN `updated_at`;" +
|
||||
"ALTER TABLE `{{admins}}` DROP COLUMN `created_at`;" +
|
||||
"ALTER TABLE `{{admins}}` DROP COLUMN `last_login`;" +
|
||||
"ALTER TABLE `{{users}}` DROP COLUMN `created_at`;" +
|
||||
"ALTER TABLE `{{users}}` DROP COLUMN `updated_at`;"
|
||||
)
|
||||
|
||||
// MySQLProvider auth provider for MySQL/MariaDB database
|
||||
|
@ -117,10 +134,18 @@ func (p *MySQLProvider) getUsedQuota(username string) (int, int64, error) {
|
|||
return sqlCommonGetUsedQuota(username, p.dbHandle)
|
||||
}
|
||||
|
||||
func (p *MySQLProvider) setUpdatedAt(username string) {
|
||||
sqlCommonSetUpdatedAt(username, p.dbHandle)
|
||||
}
|
||||
|
||||
func (p *MySQLProvider) updateLastLogin(username string) error {
|
||||
return sqlCommonUpdateLastLogin(username, p.dbHandle)
|
||||
}
|
||||
|
||||
func (p *MySQLProvider) updateAdminLastLogin(username string) error {
|
||||
return sqlCommonUpdateAdminLastLogin(username, p.dbHandle)
|
||||
}
|
||||
|
||||
func (p *MySQLProvider) userExists(username string) (User, error) {
|
||||
return sqlCommonGetUserByUsername(username, p.dbHandle)
|
||||
}
|
||||
|
@ -276,6 +301,8 @@ func (p *MySQLProvider) migrateDatabase() error {
|
|||
return err
|
||||
case version == 10:
|
||||
return updateMySQLDatabaseFromV10(p.dbHandle)
|
||||
case version == 11:
|
||||
return updateMySQLDatabaseFromV11(p.dbHandle)
|
||||
default:
|
||||
if version > sqlDatabaseVersion {
|
||||
providerLog(logger.LevelWarn, "database version %v is newer than the supported one: %v", version,
|
||||
|
@ -298,6 +325,8 @@ func (p *MySQLProvider) revertDatabase(targetVersion int) error {
|
|||
}
|
||||
|
||||
switch dbVersion.Version {
|
||||
case 12:
|
||||
return downgradeMySQLDatabaseFromV12(p.dbHandle)
|
||||
case 11:
|
||||
return downgradeMySQLDatabaseFromV11(p.dbHandle)
|
||||
default:
|
||||
|
@ -306,13 +335,45 @@ func (p *MySQLProvider) revertDatabase(targetVersion int) error {
|
|||
}
|
||||
|
||||
func updateMySQLDatabaseFromV10(dbHandle *sql.DB) error {
|
||||
return updateMySQLDatabaseFrom10To11(dbHandle)
|
||||
if err := updateMySQLDatabaseFrom10To11(dbHandle); err != nil {
|
||||
return err
|
||||
}
|
||||
return updateMySQLDatabaseFromV11(dbHandle)
|
||||
}
|
||||
|
||||
func updateMySQLDatabaseFromV11(dbHandle *sql.DB) error {
|
||||
return updateMySQLDatabaseFrom11To12(dbHandle)
|
||||
}
|
||||
|
||||
func downgradeMySQLDatabaseFromV12(dbHandle *sql.DB) error {
|
||||
if err := downgradeMySQLDatabaseFrom12To11(dbHandle); err != nil {
|
||||
return err
|
||||
}
|
||||
return downgradeMySQLDatabaseFromV11(dbHandle)
|
||||
}
|
||||
|
||||
func downgradeMySQLDatabaseFromV11(dbHandle *sql.DB) error {
|
||||
return downgradeMySQLDatabaseFrom11To10(dbHandle)
|
||||
}
|
||||
|
||||
func updateMySQLDatabaseFrom11To12(dbHandle *sql.DB) error {
|
||||
logger.InfoToConsole("updating database version: 11 -> 12")
|
||||
providerLog(logger.LevelInfo, "updating database version: 11 -> 12")
|
||||
sql := strings.ReplaceAll(mysqlV12SQL, "{{users}}", sqlTableUsers)
|
||||
sql = strings.ReplaceAll(sql, "{{admins}}", sqlTableAdmins)
|
||||
sql = strings.ReplaceAll(sql, "{{prefix}}", config.SQLTablesPrefix)
|
||||
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, strings.Split(sql, ";"), 12)
|
||||
}
|
||||
|
||||
func downgradeMySQLDatabaseFrom12To11(dbHandle *sql.DB) error {
|
||||
logger.InfoToConsole("downgrading database version: 12 -> 11")
|
||||
providerLog(logger.LevelInfo, "downgrading database version: 12 -> 11")
|
||||
sql := strings.ReplaceAll(mysqlV12DownSQL, "{{users}}", sqlTableUsers)
|
||||
sql = strings.ReplaceAll(sql, "{{admins}}", sqlTableAdmins)
|
||||
sql = strings.ReplaceAll(sql, "{{prefix}}", config.SQLTablesPrefix)
|
||||
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, strings.Split(sql, ";"), 11)
|
||||
}
|
||||
|
||||
func updateMySQLDatabaseFrom10To11(dbHandle *sql.DB) error {
|
||||
logger.InfoToConsole("updating database version: 10 -> 11")
|
||||
providerLog(logger.LevelInfo, "updating database version: 10 -> 11")
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build nomysql
|
||||
// +build nomysql
|
||||
|
||||
package dataprovider
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !nopgsql
|
||||
// +build !nopgsql
|
||||
|
||||
package dataprovider
|
||||
|
@ -57,6 +58,24 @@ CREATE INDEX "{{prefix}}api_keys_admin_id_idx" ON "{{api_keys}}" ("admin_id");
|
|||
CREATE INDEX "{{prefix}}api_keys_user_id_idx" ON "{{api_keys}}" ("user_id");
|
||||
`
|
||||
pgsqlV11DownSQL = `DROP TABLE "{{api_keys}}" CASCADE;`
|
||||
pgsqlV12SQL = `ALTER TABLE "{{admins}}" ADD COLUMN "created_at" bigint DEFAULT 0 NOT NULL;
|
||||
ALTER TABLE "{{admins}}" ALTER COLUMN "created_at" DROP DEFAULT;
|
||||
ALTER TABLE "{{admins}}" ADD COLUMN "updated_at" bigint DEFAULT 0 NOT NULL;
|
||||
ALTER TABLE "{{admins}}" ALTER COLUMN "updated_at" DROP DEFAULT;
|
||||
ALTER TABLE "{{admins}}" ADD COLUMN "last_login" bigint DEFAULT 0 NOT NULL;
|
||||
ALTER TABLE "{{admins}}" ALTER COLUMN "last_login" DROP DEFAULT;
|
||||
ALTER TABLE "{{users}}" ADD COLUMN "created_at" bigint DEFAULT 0 NOT NULL;
|
||||
ALTER TABLE "{{users}}" ALTER COLUMN "created_at" DROP DEFAULT;
|
||||
ALTER TABLE "{{users}}" ADD COLUMN "updated_at" bigint DEFAULT 0 NOT NULL;
|
||||
ALTER TABLE "{{users}}" ALTER COLUMN "updated_at" DROP DEFAULT;
|
||||
CREATE INDEX "{{prefix}}users_updated_at_idx" ON "{{users}}" ("updated_at");
|
||||
`
|
||||
pgsqlV12DownSQL = `ALTER TABLE "{{users}}" DROP COLUMN "updated_at" CASCADE;
|
||||
ALTER TABLE "{{users}}" DROP COLUMN "created_at" CASCADE;
|
||||
ALTER TABLE "{{admins}}" DROP COLUMN "created_at" CASCADE;
|
||||
ALTER TABLE "{{admins}}" DROP COLUMN "updated_at" CASCADE;
|
||||
ALTER TABLE "{{admins}}" DROP COLUMN "last_login" CASCADE;
|
||||
`
|
||||
)
|
||||
|
||||
// PGSQLProvider auth provider for PostgreSQL database
|
||||
|
@ -128,10 +147,18 @@ func (p *PGSQLProvider) getUsedQuota(username string) (int, int64, error) {
|
|||
return sqlCommonGetUsedQuota(username, p.dbHandle)
|
||||
}
|
||||
|
||||
func (p *PGSQLProvider) setUpdatedAt(username string) {
|
||||
sqlCommonSetUpdatedAt(username, p.dbHandle)
|
||||
}
|
||||
|
||||
func (p *PGSQLProvider) updateLastLogin(username string) error {
|
||||
return sqlCommonUpdateLastLogin(username, p.dbHandle)
|
||||
}
|
||||
|
||||
func (p *PGSQLProvider) updateAdminLastLogin(username string) error {
|
||||
return sqlCommonUpdateAdminLastLogin(username, p.dbHandle)
|
||||
}
|
||||
|
||||
func (p *PGSQLProvider) userExists(username string) (User, error) {
|
||||
return sqlCommonGetUserByUsername(username, p.dbHandle)
|
||||
}
|
||||
|
@ -293,6 +320,8 @@ func (p *PGSQLProvider) migrateDatabase() error {
|
|||
return err
|
||||
case version == 10:
|
||||
return updatePGSQLDatabaseFromV10(p.dbHandle)
|
||||
case version == 11:
|
||||
return updatePGSQLDatabaseFromV11(p.dbHandle)
|
||||
default:
|
||||
if version > sqlDatabaseVersion {
|
||||
providerLog(logger.LevelWarn, "database version %v is newer than the supported one: %v", version,
|
||||
|
@ -315,6 +344,8 @@ func (p *PGSQLProvider) revertDatabase(targetVersion int) error {
|
|||
}
|
||||
|
||||
switch dbVersion.Version {
|
||||
case 12:
|
||||
return downgradePGSQLDatabaseFromV12(p.dbHandle)
|
||||
case 11:
|
||||
return downgradePGSQLDatabaseFromV11(p.dbHandle)
|
||||
default:
|
||||
|
@ -323,13 +354,45 @@ func (p *PGSQLProvider) revertDatabase(targetVersion int) error {
|
|||
}
|
||||
|
||||
func updatePGSQLDatabaseFromV10(dbHandle *sql.DB) error {
|
||||
return updatePGSQLDatabaseFrom10To11(dbHandle)
|
||||
if err := updatePGSQLDatabaseFrom10To11(dbHandle); err != nil {
|
||||
return err
|
||||
}
|
||||
return updatePGSQLDatabaseFromV11(dbHandle)
|
||||
}
|
||||
|
||||
func updatePGSQLDatabaseFromV11(dbHandle *sql.DB) error {
|
||||
return updatePGSQLDatabaseFrom11To12(dbHandle)
|
||||
}
|
||||
|
||||
func downgradePGSQLDatabaseFromV12(dbHandle *sql.DB) error {
|
||||
if err := downgradePGSQLDatabaseFrom12To11(dbHandle); err != nil {
|
||||
return err
|
||||
}
|
||||
return downgradePGSQLDatabaseFromV11(dbHandle)
|
||||
}
|
||||
|
||||
func downgradePGSQLDatabaseFromV11(dbHandle *sql.DB) error {
|
||||
return downgradePGSQLDatabaseFrom11To10(dbHandle)
|
||||
}
|
||||
|
||||
func updatePGSQLDatabaseFrom11To12(dbHandle *sql.DB) error {
|
||||
logger.InfoToConsole("updating database version: 11 -> 12")
|
||||
providerLog(logger.LevelInfo, "updating database version: 11 -> 12")
|
||||
sql := strings.ReplaceAll(pgsqlV12SQL, "{{users}}", sqlTableUsers)
|
||||
sql = strings.ReplaceAll(sql, "{{admins}}", sqlTableAdmins)
|
||||
sql = strings.ReplaceAll(sql, "{{prefix}}", config.SQLTablesPrefix)
|
||||
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 12)
|
||||
}
|
||||
|
||||
func downgradePGSQLDatabaseFrom12To11(dbHandle *sql.DB) error {
|
||||
logger.InfoToConsole("downgrading database version: 12 -> 11")
|
||||
providerLog(logger.LevelInfo, "downgrading database version: 12 -> 11")
|
||||
sql := strings.ReplaceAll(pgsqlV12DownSQL, "{{users}}", sqlTableUsers)
|
||||
sql = strings.ReplaceAll(sql, "{{admins}}", sqlTableAdmins)
|
||||
sql = strings.ReplaceAll(sql, "{{prefix}}", config.SQLTablesPrefix)
|
||||
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 11)
|
||||
}
|
||||
|
||||
func updatePGSQLDatabaseFrom10To11(dbHandle *sql.DB) error {
|
||||
logger.InfoToConsole("updating database version: 10 -> 11")
|
||||
providerLog(logger.LevelInfo, "updating database version: 10 -> 11")
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build nopgsql
|
||||
// +build nopgsql
|
||||
|
||||
package dataprovider
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
sqlDatabaseVersion = 11
|
||||
sqlDatabaseVersion = 12
|
||||
defaultSQLQueryTimeout = 10 * time.Second
|
||||
longSQLQueryTimeout = 60 * time.Second
|
||||
)
|
||||
|
@ -75,7 +75,7 @@ func sqlCommonAddAPIKey(apiKey *APIKey, dbHandle *sql.DB) error {
|
|||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = stmt.ExecContext(ctx, apiKey.KeyID, apiKey.Name, apiKey.Key, apiKey.Scope, apiKey.CreatedAt,
|
||||
_, err = stmt.ExecContext(ctx, apiKey.KeyID, apiKey.Name, apiKey.Key, apiKey.Scope, util.GetTimeAsMsSinceEpoch(time.Now()),
|
||||
util.GetTimeAsMsSinceEpoch(time.Now()), apiKey.LastUseAt, apiKey.ExpiresAt, apiKey.Description,
|
||||
userID, adminID)
|
||||
return err
|
||||
|
@ -251,7 +251,8 @@ func sqlCommonAddAdmin(admin *Admin, dbHandle *sql.DB) error {
|
|||
}
|
||||
|
||||
_, err = stmt.ExecContext(ctx, admin.Username, admin.Password, admin.Status, admin.Email, string(perms),
|
||||
string(filters), admin.AdditionalInfo, admin.Description)
|
||||
string(filters), admin.AdditionalInfo, admin.Description, util.GetTimeAsMsSinceEpoch(time.Now()),
|
||||
util.GetTimeAsMsSinceEpoch(time.Now()))
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -282,7 +283,7 @@ func sqlCommonUpdateAdmin(admin *Admin, dbHandle *sql.DB) error {
|
|||
}
|
||||
|
||||
_, err = stmt.ExecContext(ctx, admin.Password, admin.Status, admin.Email, string(perms), string(filters),
|
||||
admin.AdditionalInfo, admin.Description, admin.Username)
|
||||
admin.AdditionalInfo, admin.Description, util.GetTimeAsMsSinceEpoch(time.Now()), admin.Username)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -486,6 +487,43 @@ func sqlCommonUpdateAPIKeyLastUse(keyID string, dbHandle *sql.DB) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func sqlCommonUpdateAdminLastLogin(username string, dbHandle *sql.DB) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultSQLQueryTimeout)
|
||||
defer cancel()
|
||||
q := getUpdateAdminLastLoginQuery()
|
||||
stmt, err := dbHandle.PrepareContext(ctx, q)
|
||||
if err != nil {
|
||||
providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err)
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
_, err = stmt.ExecContext(ctx, util.GetTimeAsMsSinceEpoch(time.Now()), username)
|
||||
if err == nil {
|
||||
providerLog(logger.LevelDebug, "last login updated for admin %#v", username)
|
||||
} else {
|
||||
providerLog(logger.LevelWarn, "error updating last login for admin %#v: %v", username, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func sqlCommonSetUpdatedAt(username string, dbHandle *sql.DB) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultSQLQueryTimeout)
|
||||
defer cancel()
|
||||
q := getSetUpdateAtQuery()
|
||||
stmt, err := dbHandle.PrepareContext(ctx, q)
|
||||
if err != nil {
|
||||
providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err)
|
||||
return
|
||||
}
|
||||
defer stmt.Close()
|
||||
_, err = stmt.ExecContext(ctx, util.GetTimeAsMsSinceEpoch(time.Now()), username)
|
||||
if err == nil {
|
||||
providerLog(logger.LevelDebug, "updated_at set for user %#v", username)
|
||||
} else {
|
||||
providerLog(logger.LevelWarn, "error setting updated_at for user %#v: %v", username, err)
|
||||
}
|
||||
}
|
||||
|
||||
func sqlCommonUpdateLastLogin(username string, dbHandle *sql.DB) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultSQLQueryTimeout)
|
||||
defer cancel()
|
||||
|
@ -539,7 +577,8 @@ func sqlCommonAddUser(user *User, dbHandle *sql.DB) error {
|
|||
}
|
||||
_, err = stmt.ExecContext(ctx, user.Username, user.Password, string(publicKeys), user.HomeDir, user.UID, user.GID, user.MaxSessions, user.QuotaSize,
|
||||
user.QuotaFiles, string(permissions), user.UploadBandwidth, user.DownloadBandwidth, user.Status, user.ExpirationDate, string(filters),
|
||||
string(fsConfig), user.AdditionalInfo, user.Description)
|
||||
string(fsConfig), user.AdditionalInfo, user.Description, util.GetTimeAsMsSinceEpoch(time.Now()),
|
||||
util.GetTimeAsMsSinceEpoch(time.Now()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -581,7 +620,7 @@ func sqlCommonUpdateUser(user *User, dbHandle *sql.DB) error {
|
|||
}
|
||||
_, err = stmt.ExecContext(ctx, user.Password, string(publicKeys), user.HomeDir, user.UID, user.GID, user.MaxSessions, user.QuotaSize,
|
||||
user.QuotaFiles, string(permissions), user.UploadBandwidth, user.DownloadBandwidth, user.Status, user.ExpirationDate,
|
||||
string(filters), string(fsConfig), user.AdditionalInfo, user.Description, user.ID)
|
||||
string(filters), string(fsConfig), user.AdditionalInfo, user.Description, util.GetTimeAsMsSinceEpoch(time.Now()), user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -702,7 +741,7 @@ func getAdminFromDbRow(row sqlScanner) (Admin, error) {
|
|||
var email, filters, additionalInfo, permissions, description sql.NullString
|
||||
|
||||
err := row.Scan(&admin.ID, &admin.Username, &admin.Password, &admin.Status, &email, &permissions,
|
||||
&filters, &additionalInfo, &description)
|
||||
&filters, &additionalInfo, &description, &admin.CreatedAt, &admin.UpdatedAt, &admin.LastLogin)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
|
@ -752,7 +791,7 @@ func getUserFromDbRow(row sqlScanner) (User, error) {
|
|||
err := row.Scan(&user.ID, &user.Username, &password, &publicKey, &user.HomeDir, &user.UID, &user.GID, &user.MaxSessions,
|
||||
&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)
|
||||
&additionalInfo, &description, &user.CreatedAt, &user.UpdatedAt)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return user, util.NewRecordNotFoundError(err.Error())
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !nosqlite
|
||||
// +build !nosqlite
|
||||
|
||||
package dataprovider
|
||||
|
@ -52,6 +53,20 @@ CREATE INDEX "{{prefix}}api_keys_admin_id_idx" ON "api_keys" ("admin_id");
|
|||
CREATE INDEX "{{prefix}}api_keys_user_id_idx" ON "api_keys" ("user_id");
|
||||
`
|
||||
sqliteV11DownSQL = `DROP TABLE "{{api_keys}}";`
|
||||
sqliteV12SQL = `ALTER TABLE "{{admins}}" ADD COLUMN "created_at" bigint DEFAULT 0 NOT NULL;
|
||||
ALTER TABLE "{{admins}}" ADD COLUMN "updated_at" bigint DEFAULT 0 NOT NULL;
|
||||
ALTER TABLE "{{admins}}" ADD COLUMN "last_login" bigint DEFAULT 0 NOT NULL;
|
||||
ALTER TABLE "{{users}}" ADD COLUMN "created_at" bigint DEFAULT 0 NOT NULL;
|
||||
ALTER TABLE "{{users}}" ADD COLUMN "updated_at" bigint DEFAULT 0 NOT NULL;
|
||||
CREATE INDEX "{{prefix}}users_updated_at_idx" ON "{{users}}" ("updated_at");
|
||||
`
|
||||
sqliteV12DownSQL = `DROP INDEX "{{prefix}}users_updated_at_idx";
|
||||
ALTER TABLE "{{users}}" DROP COLUMN "updated_at";
|
||||
ALTER TABLE "{{users}}" DROP COLUMN "created_at";
|
||||
ALTER TABLE "{{admins}}" DROP COLUMN "created_at";
|
||||
ALTER TABLE "{{admins}}" DROP COLUMN "updated_at";
|
||||
ALTER TABLE "{{admins}}" DROP COLUMN "last_login";
|
||||
`
|
||||
)
|
||||
|
||||
// SQLiteProvider auth provider for SQLite database
|
||||
|
@ -115,10 +130,18 @@ func (p *SQLiteProvider) getUsedQuota(username string) (int, int64, error) {
|
|||
return sqlCommonGetUsedQuota(username, p.dbHandle)
|
||||
}
|
||||
|
||||
func (p *SQLiteProvider) setUpdatedAt(username string) {
|
||||
sqlCommonSetUpdatedAt(username, p.dbHandle)
|
||||
}
|
||||
|
||||
func (p *SQLiteProvider) updateLastLogin(username string) error {
|
||||
return sqlCommonUpdateLastLogin(username, p.dbHandle)
|
||||
}
|
||||
|
||||
func (p *SQLiteProvider) updateAdminLastLogin(username string) error {
|
||||
return sqlCommonUpdateAdminLastLogin(username, p.dbHandle)
|
||||
}
|
||||
|
||||
func (p *SQLiteProvider) userExists(username string) (User, error) {
|
||||
return sqlCommonGetUserByUsername(username, p.dbHandle)
|
||||
}
|
||||
|
@ -274,6 +297,8 @@ func (p *SQLiteProvider) migrateDatabase() error {
|
|||
return err
|
||||
case version == 10:
|
||||
return updateSQLiteDatabaseFromV10(p.dbHandle)
|
||||
case version == 11:
|
||||
return updateSQLiteDatabaseFromV11(p.dbHandle)
|
||||
default:
|
||||
if version > sqlDatabaseVersion {
|
||||
providerLog(logger.LevelWarn, "database version %v is newer than the supported one: %v", version,
|
||||
|
@ -296,6 +321,8 @@ func (p *SQLiteProvider) revertDatabase(targetVersion int) error {
|
|||
}
|
||||
|
||||
switch dbVersion.Version {
|
||||
case 12:
|
||||
return downgradeSQLiteDatabaseFromV12(p.dbHandle)
|
||||
case 11:
|
||||
return downgradeSQLiteDatabaseFromV11(p.dbHandle)
|
||||
default:
|
||||
|
@ -304,13 +331,45 @@ func (p *SQLiteProvider) revertDatabase(targetVersion int) error {
|
|||
}
|
||||
|
||||
func updateSQLiteDatabaseFromV10(dbHandle *sql.DB) error {
|
||||
return updateSQLiteDatabaseFrom10To11(dbHandle)
|
||||
if err := updateSQLiteDatabaseFrom10To11(dbHandle); err != nil {
|
||||
return err
|
||||
}
|
||||
return updateSQLiteDatabaseFromV11(dbHandle)
|
||||
}
|
||||
|
||||
func updateSQLiteDatabaseFromV11(dbHandle *sql.DB) error {
|
||||
return updateSQLiteDatabaseFrom11To12(dbHandle)
|
||||
}
|
||||
|
||||
func downgradeSQLiteDatabaseFromV12(dbHandle *sql.DB) error {
|
||||
if err := downgradeSQLiteDatabaseFrom12To11(dbHandle); err != nil {
|
||||
return err
|
||||
}
|
||||
return downgradeSQLiteDatabaseFromV11(dbHandle)
|
||||
}
|
||||
|
||||
func downgradeSQLiteDatabaseFromV11(dbHandle *sql.DB) error {
|
||||
return downgradeSQLiteDatabaseFrom11To10(dbHandle)
|
||||
}
|
||||
|
||||
func updateSQLiteDatabaseFrom11To12(dbHandle *sql.DB) error {
|
||||
logger.InfoToConsole("updating database version: 11 -> 12")
|
||||
providerLog(logger.LevelInfo, "updating database version: 11 -> 12")
|
||||
sql := strings.ReplaceAll(sqliteV12SQL, "{{users}}", sqlTableUsers)
|
||||
sql = strings.ReplaceAll(sql, "{{admins}}", sqlTableAdmins)
|
||||
sql = strings.ReplaceAll(sql, "{{prefix}}", config.SQLTablesPrefix)
|
||||
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 12)
|
||||
}
|
||||
|
||||
func downgradeSQLiteDatabaseFrom12To11(dbHandle *sql.DB) error {
|
||||
logger.InfoToConsole("downgrading database version: 12 -> 11")
|
||||
providerLog(logger.LevelInfo, "downgrading database version: 12 -> 11")
|
||||
sql := strings.ReplaceAll(sqliteV12DownSQL, "{{users}}", sqlTableUsers)
|
||||
sql = strings.ReplaceAll(sql, "{{admins}}", sqlTableAdmins)
|
||||
sql = strings.ReplaceAll(sql, "{{prefix}}", config.SQLTablesPrefix)
|
||||
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 11)
|
||||
}
|
||||
|
||||
func updateSQLiteDatabaseFrom10To11(dbHandle *sql.DB) error {
|
||||
logger.InfoToConsole("updating database version: 10 -> 11")
|
||||
providerLog(logger.LevelInfo, "updating database version: 10 -> 11")
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build nosqlite
|
||||
// +build nosqlite
|
||||
|
||||
package dataprovider
|
||||
|
|
|
@ -11,9 +11,9 @@ import (
|
|||
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"
|
||||
"additional_info,description,created_at,updated_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"
|
||||
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"
|
||||
)
|
||||
|
||||
|
@ -43,15 +43,16 @@ func getDumpAdminsQuery() string {
|
|||
}
|
||||
|
||||
func getAddAdminQuery() string {
|
||||
return fmt.Sprintf(`INSERT INTO %v (username,password,status,email,permissions,filters,additional_info,description)
|
||||
VALUES (%v,%v,%v,%v,%v,%v,%v,%v)`, sqlTableAdmins, sqlPlaceholders[0], sqlPlaceholders[1],
|
||||
sqlPlaceholders[2], sqlPlaceholders[3], sqlPlaceholders[4], sqlPlaceholders[5], sqlPlaceholders[6], sqlPlaceholders[7])
|
||||
return fmt.Sprintf(`INSERT INTO %v (username,password,status,email,permissions,filters,additional_info,description,created_at,updated_at,last_login)
|
||||
VALUES (%v,%v,%v,%v,%v,%v,%v,%v,%v,%v,0)`, sqlTableAdmins, sqlPlaceholders[0], sqlPlaceholders[1],
|
||||
sqlPlaceholders[2], sqlPlaceholders[3], sqlPlaceholders[4], sqlPlaceholders[5], sqlPlaceholders[6], sqlPlaceholders[7],
|
||||
sqlPlaceholders[8], sqlPlaceholders[9])
|
||||
}
|
||||
|
||||
func getUpdateAdminQuery() string {
|
||||
return fmt.Sprintf(`UPDATE %v SET password=%v,status=%v,email=%v,permissions=%v,filters=%v,additional_info=%v,description=%v
|
||||
return fmt.Sprintf(`UPDATE %v SET password=%v,status=%v,email=%v,permissions=%v,filters=%v,additional_info=%v,description=%v,updated_at=%v
|
||||
WHERE username = %v`, sqlTableAdmins, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2],
|
||||
sqlPlaceholders[3], sqlPlaceholders[4], sqlPlaceholders[5], sqlPlaceholders[6], sqlPlaceholders[7])
|
||||
sqlPlaceholders[3], sqlPlaceholders[4], sqlPlaceholders[5], sqlPlaceholders[6], sqlPlaceholders[7], sqlPlaceholders[8])
|
||||
}
|
||||
|
||||
func getDeleteAdminQuery() string {
|
||||
|
@ -156,10 +157,18 @@ func getUpdateQuotaQuery(reset bool) string {
|
|||
WHERE username = %v`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3])
|
||||
}
|
||||
|
||||
func getSetUpdateAtQuery() string {
|
||||
return fmt.Sprintf(`UPDATE %v SET updated_at = %v WHERE username = %v`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1])
|
||||
}
|
||||
|
||||
func getUpdateLastLoginQuery() string {
|
||||
return fmt.Sprintf(`UPDATE %v SET last_login = %v WHERE username = %v`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1])
|
||||
}
|
||||
|
||||
func getUpdateAdminLastLoginQuery() string {
|
||||
return fmt.Sprintf(`UPDATE %v SET last_login = %v WHERE username = %v`, sqlTableAdmins, sqlPlaceholders[0], sqlPlaceholders[1])
|
||||
}
|
||||
|
||||
func getUpdateAPIKeyLastUseQuery() string {
|
||||
return fmt.Sprintf(`UPDATE %v SET last_use_at = %v WHERE key_id = %v`, sqlTableAPIKeys, sqlPlaceholders[0], sqlPlaceholders[1])
|
||||
}
|
||||
|
@ -172,20 +181,20 @@ func getQuotaQuery() string {
|
|||
func getAddUserQuery() string {
|
||||
return fmt.Sprintf(`INSERT INTO %v (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,status,last_login,expiration_date,filters,
|
||||
filesystem,additional_info,description)
|
||||
VALUES (%v,%v,%v,%v,%v,%v,%v,%v,%v,%v,0,0,0,%v,%v,%v,0,%v,%v,%v,%v,%v)`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1],
|
||||
filesystem,additional_info,description,created_at,updated_at)
|
||||
VALUES (%v,%v,%v,%v,%v,%v,%v,%v,%v,%v,0,0,0,%v,%v,%v,0,%v,%v,%v,%v,%v,%v,%v)`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1],
|
||||
sqlPlaceholders[2], sqlPlaceholders[3], sqlPlaceholders[4], sqlPlaceholders[5], sqlPlaceholders[6], sqlPlaceholders[7],
|
||||
sqlPlaceholders[8], sqlPlaceholders[9], sqlPlaceholders[10], sqlPlaceholders[11], sqlPlaceholders[12], sqlPlaceholders[13],
|
||||
sqlPlaceholders[14], sqlPlaceholders[15], sqlPlaceholders[16], sqlPlaceholders[17])
|
||||
sqlPlaceholders[14], sqlPlaceholders[15], sqlPlaceholders[16], sqlPlaceholders[17], sqlPlaceholders[18], sqlPlaceholders[19])
|
||||
}
|
||||
|
||||
func getUpdateUserQuery() string {
|
||||
return fmt.Sprintf(`UPDATE %v SET password=%v,public_keys=%v,home_dir=%v,uid=%v,gid=%v,max_sessions=%v,quota_size=%v,
|
||||
quota_files=%v,permissions=%v,upload_bandwidth=%v,download_bandwidth=%v,status=%v,expiration_date=%v,filters=%v,filesystem=%v,
|
||||
additional_info=%v,description=%v WHERE id = %v`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3],
|
||||
additional_info=%v,description=%v,updated_at=%v WHERE id = %v`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2], sqlPlaceholders[3],
|
||||
sqlPlaceholders[4], sqlPlaceholders[5], sqlPlaceholders[6], sqlPlaceholders[7], sqlPlaceholders[8], sqlPlaceholders[9],
|
||||
sqlPlaceholders[10], sqlPlaceholders[11], sqlPlaceholders[12], sqlPlaceholders[13], sqlPlaceholders[14], sqlPlaceholders[15],
|
||||
sqlPlaceholders[16], sqlPlaceholders[17])
|
||||
sqlPlaceholders[16], sqlPlaceholders[17], sqlPlaceholders[18])
|
||||
}
|
||||
|
||||
func getDeleteUserQuery() string {
|
||||
|
|
|
@ -1037,6 +1037,8 @@ func (u *User) getACopy() User {
|
|||
Filters: filters,
|
||||
AdditionalInfo: u.AdditionalInfo,
|
||||
Description: u.Description,
|
||||
CreatedAt: u.CreatedAt,
|
||||
UpdatedAt: u.UpdatedAt,
|
||||
},
|
||||
VirtualFolders: virtualFolders,
|
||||
FsConfig: u.FsConfig.GetACopy(),
|
||||
|
|
|
@ -20,12 +20,12 @@ SFTPGo provides an official Docker image, it is available on both [Docker Hub](h
|
|||
Starting a SFTPGo instance is simple:
|
||||
|
||||
```shell
|
||||
docker run --name some-sftpgo -p 127.0.0.1:8080:8080 -p 2022:2022 -d "drakkan/sftpgo:tag"
|
||||
docker run --name some-sftpgo -p 8080:8080 -p 2022:2022 -d "drakkan/sftpgo:tag"
|
||||
```
|
||||
|
||||
... where `some-sftpgo` is the name you want to assign to your container, and `tag` is the tag specifying the SFTPGo version you want. See the list above for relevant tags.
|
||||
|
||||
Now visit [http://localhost:8080/web/admin](http://localhost:8080/web/admin), create the first admin and then log in and create a new SFTPGo user. The SFTP service is available on port 2022.
|
||||
Now visit [http://localhost:8080/web/admin](http://localhost:8080/web/admin), replacing `localhost` with the appropriate IP address if SFTPGo is not reachable on localhost, create the first admin and a new SFTPGo user. The SFTP service is available on port 2022.
|
||||
|
||||
If you don't want to persist any files, for example for testing purposes, you can run an SFTPGo instance like this:
|
||||
|
||||
|
@ -102,7 +102,7 @@ The Docker documentation is a good starting point for understanding the differen
|
|||
|
||||
```shell
|
||||
docker run --name some-sftpgo \
|
||||
-p 127.0.0.1:8080:8090 \
|
||||
-p 8080:8090 \
|
||||
-p 2022:2022 \
|
||||
--mount type=bind,source=/my/own/sftpgodata,target=/srv/sftpgo \
|
||||
--mount type=bind,source=/my/own/sftpgohome,target=/var/lib/sftpgo \
|
||||
|
@ -150,7 +150,7 @@ With the above directory permissions, you can start a SFTPGo instance like this:
|
|||
```shell
|
||||
docker run --name some-sftpgo \
|
||||
--user 1100:1100 \
|
||||
-p 127.0.0.1:8080:8080 \
|
||||
-p 8080:8080 \
|
||||
-p 2022:2022 \
|
||||
--mount type=bind,source="${PWD}/data",target=/srv/sftpgo \
|
||||
--mount type=bind,source="${PWD}/config",target=/var/lib/sftpgo \
|
||||
|
|
|
@ -203,7 +203,7 @@ func (s *Server) AuthUser(cc ftpserver.ClientContext, username, password string)
|
|||
}
|
||||
connection.Log(logger.LevelInfo, "User id: %d, logged in with FTP, username: %#v, home_dir: %#v remote addr: %#v",
|
||||
user.ID, user.Username, user.HomeDir, ipAddr)
|
||||
dataprovider.UpdateLastLogin(&user) //nolint:errcheck
|
||||
dataprovider.UpdateLastLogin(&user)
|
||||
return connection, nil
|
||||
}
|
||||
|
||||
|
@ -249,7 +249,7 @@ func (s *Server) VerifyConnection(cc ftpserver.ClientContext, user string, tlsCo
|
|||
}
|
||||
connection.Log(logger.LevelInfo, "User id: %d, logged in with FTP using a TLS certificate, username: %#v, home_dir: %#v remote addr: %#v",
|
||||
dbUser.ID, dbUser.Username, dbUser.HomeDir, ipAddr)
|
||||
dataprovider.UpdateLastLogin(&dbUser) //nolint:errcheck
|
||||
dataprovider.UpdateLastLogin(&dbUser)
|
||||
return connection, nil
|
||||
}
|
||||
}
|
||||
|
|
10
go.mod
10
go.mod
|
@ -7,7 +7,7 @@ require (
|
|||
github.com/Azure/azure-storage-blob-go v0.14.0
|
||||
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
|
||||
github.com/alexedwards/argon2id v0.0.0-20210511081203-7d35d68092b8
|
||||
github.com/aws/aws-sdk-go v1.40.24
|
||||
github.com/aws/aws-sdk-go v1.40.25
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.1.1
|
||||
github.com/eikenb/pipeat v0.0.0-20210603033007-44fc3ffce52b
|
||||
github.com/fatih/color v1.12.0 // indirect
|
||||
|
@ -61,17 +61,17 @@ require (
|
|||
gocloud.dev v0.23.0
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d
|
||||
golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2
|
||||
golang.org/x/sys v0.0.0-20210819072135-bce67f096156
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
|
||||
google.golang.org/api v0.54.0
|
||||
google.golang.org/genproto v0.0.0-20210816143620-e15ff196659d // indirect
|
||||
google.golang.org/genproto v0.0.0-20210818220304-27ea9cc85d9f // indirect
|
||||
google.golang.org/grpc v1.40.0
|
||||
google.golang.org/protobuf v1.27.1
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.92.3 // indirect
|
||||
cloud.google.com/go v0.93.3 // indirect
|
||||
cloud.google.com/go/kms v0.1.0 // indirect
|
||||
github.com/Azure/azure-pipeline-go v0.2.3 // indirect
|
||||
github.com/StackExchange/wmi v1.2.1 // indirect
|
||||
|
@ -82,7 +82,7 @@ require (
|
|||
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.5 // indirect
|
||||
github.com/goccy/go-json v0.7.6 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
|
|
19
go.sum
19
go.sum
|
@ -26,8 +26,8 @@ cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSU
|
|||
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
|
||||
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
|
||||
cloud.google.com/go v0.92.2/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
|
||||
cloud.google.com/go v0.92.3 h1:VWuKmJ8pyOrb7doM0NnQDYngKv+zTicI8BaMsnIA9gA=
|
||||
cloud.google.com/go v0.92.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
|
||||
cloud.google.com/go v0.93.3 h1:wPBktZFzYBcCZVARvwVKqH1uEj+aLXofJEtrb4oOsio=
|
||||
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
|
@ -124,8 +124,8 @@ github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZo
|
|||
github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.38.68/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.40.24 h1:qtXDYFzAxEmmZaa+4JA9loBqOujO0vm4ZOJoEmjG21E=
|
||||
github.com/aws/aws-sdk-go v1.40.24/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
||||
github.com/aws/aws-sdk-go v1.40.25 h1:Depnx7O86HWgOCLD5nMto6F9Ju85Q1QuFDnbpZYQWno=
|
||||
github.com/aws/aws-sdk-go v1.40.25/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
||||
github.com/aws/aws-sdk-go-v2 v1.7.0/go.mod h1:tb9wi5s61kTDA5qCkcDbt3KRVV74GGslQkl/DRdX/P4=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.5.0/go.mod h1:acH3+MQoiMzozT/ivU+DbRg7Ooo2298RdRaWcOv+4vM=
|
||||
github.com/aws/smithy-go v1.5.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
||||
|
@ -216,8 +216,9 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu
|
|||
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
||||
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.0 h1:NO5hkcB+srp1x6QmwvNZLeaOgbM8cmBTN32THzjvu2k=
|
||||
github.com/fsnotify/fsnotify v1.5.0/go.mod h1:BX0DCEr5pT4jm2CnQdVP1lFV521fcCNcyEeNp4DQQDk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
|
||||
|
@ -915,8 +916,8 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2 h1:c8PlLMqBbOHoqtjteWm5/kbe6rNY2pbRfbIMVnepueo=
|
||||
golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210819072135-bce67f096156 h1:f7XLk/QXGE6IM4HjJ4ttFFlPSwJ65A1apfDd+mmViR0=
|
||||
golang.org/x/sys v0.0.0-20210819072135-bce67f096156/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -1112,8 +1113,8 @@ google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm
|
|||
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
|
||||
google.golang.org/genproto v0.0.0-20210816143620-e15ff196659d h1:fPtHPeysWvGVJwQFKu3B7H2DB2sOEsW7UTayKkWESKw=
|
||||
google.golang.org/genproto v0.0.0-20210816143620-e15ff196659d/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210818220304-27ea9cc85d9f h1:enWPderunHptc5pzJkSYGx0olpF8goXzG0rY3kL0eSg=
|
||||
google.golang.org/genproto v0.0.0-20210818220304-27ea9cc85d9f/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
|
|
|
@ -395,6 +395,79 @@ func TestBasicUserHandling(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestUserTimestamps(t *testing.T) {
|
||||
user, resp, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
||||
assert.NoError(t, err, string(resp))
|
||||
createdAt := user.CreatedAt
|
||||
updatedAt := user.UpdatedAt
|
||||
assert.Equal(t, int64(0), user.LastLogin)
|
||||
assert.Greater(t, createdAt, int64(0))
|
||||
assert.Greater(t, updatedAt, int64(0))
|
||||
mappedPath := filepath.Join(os.TempDir(), "mapped_dir")
|
||||
folderName := filepath.Base(mappedPath)
|
||||
user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{
|
||||
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
||||
Name: folderName,
|
||||
MappedPath: mappedPath,
|
||||
},
|
||||
VirtualPath: "/vdir",
|
||||
})
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
user, resp, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err, string(resp))
|
||||
assert.Equal(t, int64(0), user.LastLogin)
|
||||
assert.Equal(t, createdAt, user.CreatedAt)
|
||||
assert.Greater(t, user.UpdatedAt, updatedAt)
|
||||
updatedAt = user.UpdatedAt
|
||||
// after a folder update or delete the user updated_at field should change
|
||||
folder, _, err := httpdtest.GetFolderByName(folderName, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, folder.Users, 1)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
_, _, err = httpdtest.UpdateFolder(folder, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(0), user.LastLogin)
|
||||
assert.Equal(t, createdAt, user.CreatedAt)
|
||||
assert.Greater(t, user.UpdatedAt, updatedAt)
|
||||
updatedAt = user.UpdatedAt
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
_, err = httpdtest.RemoveFolder(folder, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(0), user.LastLogin)
|
||||
assert.Equal(t, createdAt, user.CreatedAt)
|
||||
assert.Greater(t, user.UpdatedAt, updatedAt)
|
||||
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestAdminTimestamps(t *testing.T) {
|
||||
admin := getTestAdmin()
|
||||
admin.Username = altAdminUsername
|
||||
admin, _, err := httpdtest.AddAdmin(admin, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
createdAt := admin.CreatedAt
|
||||
updatedAt := admin.UpdatedAt
|
||||
assert.Equal(t, int64(0), admin.LastLogin)
|
||||
assert.Greater(t, createdAt, int64(0))
|
||||
assert.Greater(t, updatedAt, int64(0))
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
admin, _, err = httpdtest.UpdateAdmin(admin, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(0), admin.LastLogin)
|
||||
assert.Equal(t, createdAt, admin.CreatedAt)
|
||||
assert.Greater(t, admin.UpdatedAt, updatedAt)
|
||||
|
||||
_, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestHTTPUserAuthentication(t *testing.T) {
|
||||
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
@ -757,6 +830,26 @@ func TestAdminInvalidCredentials(t *testing.T) {
|
|||
assert.Equal(t, dataprovider.ErrInvalidCredentials.Error(), responseHolder["error"].(string))
|
||||
}
|
||||
|
||||
func TestAdminLastLogin(t *testing.T) {
|
||||
a := getTestAdmin()
|
||||
a.Username = altAdminUsername
|
||||
a.Password = altAdminPassword
|
||||
|
||||
admin, _, err := httpdtest.AddAdmin(a, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(0), admin.LastLogin)
|
||||
|
||||
_, _, err = httpdtest.GetToken(altAdminUsername, altAdminPassword)
|
||||
assert.NoError(t, err)
|
||||
|
||||
admin, _, err = httpdtest.GetAdminByUsername(altAdminUsername, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, admin.LastLogin, int64(0))
|
||||
|
||||
_, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestAdminAllowList(t *testing.T) {
|
||||
a := getTestAdmin()
|
||||
a.Username = altAdminUsername
|
||||
|
@ -1361,7 +1454,7 @@ func TestUpdateUserEmptyPassword(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
userNoPwd, _, err := httpdtest.UpdateUserWithJSON(user, http.StatusOK, "", asJSON)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, user, userNoPwd) // the password is hidden so the user must be equal
|
||||
assert.Equal(t, user.Password, userNoPwd.Password) // the password is hidden
|
||||
// check the password within the data provider
|
||||
dbUser, err = dataprovider.UserExists(u.Username)
|
||||
assert.NoError(t, err)
|
||||
|
@ -1705,6 +1798,7 @@ func TestUserS3Config(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
user.Password = defaultPassword
|
||||
user.ID = 0
|
||||
user.CreatedAt = 0
|
||||
user.VirtualFolders = nil
|
||||
secret := kms.NewSecret(kms.SecretStatusSecretBox, "Server-Access-Secret", "", "")
|
||||
user.FsConfig.S3Config.AccessSecret = secret
|
||||
|
@ -1749,6 +1843,7 @@ func TestUserS3Config(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
user.Password = defaultPassword
|
||||
user.ID = 0
|
||||
user.CreatedAt = 0
|
||||
// shared credential test for add instead of update
|
||||
user, _, err = httpdtest.AddUser(user, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
@ -1795,6 +1890,7 @@ func TestUserGCSConfig(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
user.Password = defaultPassword
|
||||
user.ID = 0
|
||||
user.CreatedAt = 0
|
||||
user.FsConfig.GCSConfig.Credentials = kms.NewSecret(kms.SecretStatusSecretBox, "fake credentials", "", "")
|
||||
_, _, err = httpdtest.AddUser(user, http.StatusCreated)
|
||||
assert.Error(t, err)
|
||||
|
@ -1861,6 +1957,7 @@ func TestUserAzureBlobConfig(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
user.Password = defaultPassword
|
||||
user.ID = 0
|
||||
user.CreatedAt = 0
|
||||
secret := kms.NewSecret(kms.SecretStatusSecretBox, "Server-Account-Key", "", "")
|
||||
user.FsConfig.AzBlobConfig.AccountKey = secret
|
||||
_, _, err = httpdtest.AddUser(user, http.StatusCreated)
|
||||
|
@ -1901,6 +1998,7 @@ func TestUserAzureBlobConfig(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
user.Password = defaultPassword
|
||||
user.ID = 0
|
||||
user.CreatedAt = 0
|
||||
// sas test for add instead of update
|
||||
user.FsConfig.AzBlobConfig = vfs.AzBlobFsConfig{
|
||||
AzBlobFsConfig: sdk.AzBlobFsConfig{
|
||||
|
@ -1956,6 +2054,7 @@ func TestUserCryptFs(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
user.Password = defaultPassword
|
||||
user.ID = 0
|
||||
user.CreatedAt = 0
|
||||
secret := kms.NewSecret(kms.SecretStatusSecretBox, "invalid encrypted payload", "", "")
|
||||
user.FsConfig.CryptConfig.Passphrase = secret
|
||||
_, _, err = httpdtest.AddUser(user, http.StatusCreated)
|
||||
|
@ -2036,6 +2135,7 @@ func TestUserSFTPFs(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
user.Password = defaultPassword
|
||||
user.ID = 0
|
||||
user.CreatedAt = 0
|
||||
secret := kms.NewSecret(kms.SecretStatusSecretBox, "invalid encrypted payload", "", "")
|
||||
user.FsConfig.SFTPConfig.Password = secret
|
||||
_, _, err = httpdtest.AddUser(user, http.StatusCreated)
|
||||
|
@ -4120,6 +4220,69 @@ func TestUpdateAdminMock(t *testing.T) {
|
|||
checkResponseCode(t, http.StatusOK, rr)
|
||||
}
|
||||
|
||||
func TestAdminLastLoginWithAPIKey(t *testing.T) {
|
||||
admin := getTestAdmin()
|
||||
admin.Username = altAdminUsername
|
||||
admin.Filters.AllowAPIKeyAuth = true
|
||||
admin, resp, err := httpdtest.AddAdmin(admin, http.StatusCreated)
|
||||
assert.NoError(t, err, string(resp))
|
||||
assert.Equal(t, int64(0), admin.LastLogin)
|
||||
|
||||
apiKey := dataprovider.APIKey{
|
||||
Name: "admin API key",
|
||||
Scope: dataprovider.APIKeyScopeAdmin,
|
||||
Admin: altAdminUsername,
|
||||
}
|
||||
|
||||
apiKey, resp, err = httpdtest.AddAPIKey(apiKey, http.StatusCreated)
|
||||
assert.NoError(t, err, string(resp))
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, versionPath, nil)
|
||||
assert.NoError(t, err)
|
||||
setAPIKeyForReq(req, apiKey.Key, admin.Username)
|
||||
rr := executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
|
||||
admin, _, err = httpdtest.GetAdminByUsername(altAdminUsername, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, admin.LastLogin, int64(0))
|
||||
|
||||
_, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestUserLastLoginWithAPIKey(t *testing.T) {
|
||||
user := getTestUser()
|
||||
user.Filters.AllowAPIKeyAuth = true
|
||||
user, resp, err := httpdtest.AddUser(user, http.StatusCreated)
|
||||
assert.NoError(t, err, string(resp))
|
||||
assert.Equal(t, int64(0), user.LastLogin)
|
||||
|
||||
apiKey := dataprovider.APIKey{
|
||||
Name: "user API key",
|
||||
Scope: dataprovider.APIKeyScopeUser,
|
||||
User: user.Username,
|
||||
}
|
||||
|
||||
apiKey, resp, err = httpdtest.AddAPIKey(apiKey, http.StatusCreated)
|
||||
assert.NoError(t, err, string(resp))
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, userDirsPath, nil)
|
||||
assert.NoError(t, err)
|
||||
setAPIKeyForReq(req, apiKey.Key, "")
|
||||
rr := executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
|
||||
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, user.LastLogin, int64(0))
|
||||
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestAdminHandlingWithAPIKeys(t *testing.T) {
|
||||
sysAdmin, _, err := httpdtest.GetAdminByUsername(defaultTokenAuthUser, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
|
@ -4260,6 +4423,8 @@ func TestUserHandlingWithAPIKey(t *testing.T) {
|
|||
setAPIKeyForReq(req, apiKey.Key, "")
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
|
@ -4506,6 +4671,7 @@ func TestUpdateUserInvalidParamsMock(t *testing.T) {
|
|||
checkResponseCode(t, http.StatusBadRequest, rr)
|
||||
userID := user.ID
|
||||
user.ID = 0
|
||||
user.CreatedAt = 0
|
||||
userAsJSON = getUserAsJSON(t, user)
|
||||
req, _ = http.NewRequest(http.MethodPut, path.Join(userPath, user.Username), bytes.NewBuffer(userAsJSON))
|
||||
setBearerForReq(req, token)
|
||||
|
|
|
@ -341,7 +341,7 @@ func authenticateAdminWithAPIKey(username, keyID string, tokenAuth *jwtauth.JWTA
|
|||
return err
|
||||
}
|
||||
r.Header.Set("Authorization", fmt.Sprintf("Bearer %v", resp["access_token"]))
|
||||
|
||||
dataprovider.UpdateAdminLastLogin(&admin)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -397,7 +397,7 @@ func authenticateUserWithAPIKey(username, keyID string, tokenAuth *jwtauth.JWTAu
|
|||
return err
|
||||
}
|
||||
r.Header.Set("Authorization", fmt.Sprintf("Bearer %v", resp["access_token"]))
|
||||
dataprovider.UpdateLastLogin(&user) //nolint:errcheck
|
||||
dataprovider.UpdateLastLogin(&user)
|
||||
updateLoginMetrics(&user, ipAddr, nil)
|
||||
|
||||
return nil
|
||||
|
|
|
@ -2969,6 +2969,14 @@ components:
|
|||
type: integer
|
||||
format: int32
|
||||
description: 'Maximum download bandwidth as KB/s, 0 means unlimited'
|
||||
created_at:
|
||||
type: integer
|
||||
format: int64
|
||||
description: 'creation time as unix timestamp in milliseconds. It will be 0 for users created before v2.2.0'
|
||||
updated_at:
|
||||
type: integer
|
||||
format: int64
|
||||
description: last update time as unix timestamp in milliseconds
|
||||
last_login:
|
||||
type: integer
|
||||
format: int64
|
||||
|
@ -3032,6 +3040,18 @@ components:
|
|||
additional_info:
|
||||
type: string
|
||||
description: Free form text field
|
||||
created_at:
|
||||
type: integer
|
||||
format: int64
|
||||
description: 'creation time as unix timestamp in milliseconds. It will be 0 for admins created before v2.2.0'
|
||||
updated_at:
|
||||
type: integer
|
||||
format: int64
|
||||
description: last update time as unix timestamp in milliseconds
|
||||
last_login:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Last user login as unix timestamp in milliseconds. It is saved at most once every 10 minutes
|
||||
APIKey:
|
||||
type: object
|
||||
properties:
|
||||
|
|
|
@ -197,7 +197,7 @@ func (s *httpdServer) handleWebClientLoginPost(w http.ResponseWriter, r *http.Re
|
|||
return
|
||||
}
|
||||
updateLoginMetrics(&user, ipAddr, err)
|
||||
dataprovider.UpdateLastLogin(&user) //nolint:errcheck
|
||||
dataprovider.UpdateLastLogin(&user)
|
||||
http.Redirect(w, r, webClientFilesPath, http.StatusFound)
|
||||
}
|
||||
|
||||
|
@ -307,6 +307,7 @@ func (s *httpdServer) loginAdmin(w http.ResponseWriter, r *http.Request, admin *
|
|||
}
|
||||
|
||||
http.Redirect(w, r, webUsersPath, http.StatusFound)
|
||||
dataprovider.UpdateAdminLastLogin(admin)
|
||||
}
|
||||
|
||||
func (s *httpdServer) logout(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -377,7 +378,7 @@ func (s *httpdServer) generateAndSendUserToken(w http.ResponseWriter, r *http.Re
|
|||
return
|
||||
}
|
||||
updateLoginMetrics(&user, ipAddr, err)
|
||||
dataprovider.UpdateLastLogin(&user) //nolint:errcheck
|
||||
dataprovider.UpdateLastLogin(&user)
|
||||
|
||||
render.JSON(w, r, resp)
|
||||
}
|
||||
|
@ -413,6 +414,7 @@ func (s *httpdServer) generateAndSendToken(w http.ResponseWriter, r *http.Reques
|
|||
return
|
||||
}
|
||||
|
||||
dataprovider.UpdateAdminLastLogin(&admin)
|
||||
render.JSON(w, r, resp)
|
||||
}
|
||||
|
||||
|
|
|
@ -1058,6 +1058,11 @@ func checkAdmin(expected, actual *dataprovider.Admin) error {
|
|||
return errors.New("admin ID mismatch")
|
||||
}
|
||||
}
|
||||
if expected.CreatedAt > 0 {
|
||||
if expected.CreatedAt != actual.CreatedAt {
|
||||
return fmt.Errorf("created_at mismatch %v != %v", expected.CreatedAt, actual.CreatedAt)
|
||||
}
|
||||
}
|
||||
if err := compareAdminEqualFields(expected, actual); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1116,6 +1121,11 @@ func checkUser(expected *dataprovider.User, actual *dataprovider.User) error {
|
|||
return errors.New("user ID mismatch")
|
||||
}
|
||||
}
|
||||
if expected.CreatedAt > 0 {
|
||||
if expected.CreatedAt != actual.CreatedAt {
|
||||
return fmt.Errorf("created_at mismatch %v != %v", expected.CreatedAt, actual.CreatedAt)
|
||||
}
|
||||
}
|
||||
if len(expected.Permissions) != len(actual.Permissions) {
|
||||
return errors.New("permissions mismatch")
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package logger
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package logger
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !nometrics
|
||||
// +build !nometrics
|
||||
|
||||
// Package metrics provides Prometheus metrics support
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build nometrics
|
||||
// +build nometrics
|
||||
|
||||
package metric
|
||||
|
|
|
@ -166,6 +166,10 @@ type BaseUser struct {
|
|||
DownloadBandwidth int64 `json:"download_bandwidth"`
|
||||
// Last login as unix timestamp in milliseconds
|
||||
LastLogin int64 `json:"last_login"`
|
||||
// Creation time as unix timestamp in milliseconds. It will be 0 for admins created before v2.2.0
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
// last update time as unix timestamp in milliseconds
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
// Additional restrictions
|
||||
Filters UserFilters `json:"filters"`
|
||||
// optional description, for example full name
|
||||
|
|
|
@ -286,15 +286,7 @@ func (s *Service) loadInitialData() error {
|
|||
}
|
||||
|
||||
func (s *Service) restoreDump(dump *dataprovider.BackupData) error {
|
||||
err := httpd.RestoreAPIKeys(dump.APIKeys, s.LoadDataFrom, s.LoadDataMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to restore API keys from file %#v: %v", s.LoadDataFrom, err)
|
||||
}
|
||||
err = httpd.RestoreAdmins(dump.Admins, s.LoadDataFrom, s.LoadDataMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to restore admins from file %#v: %v", s.LoadDataFrom, err)
|
||||
}
|
||||
err = httpd.RestoreFolders(dump.Folders, s.LoadDataFrom, s.LoadDataMode, s.LoadDataQuotaScan)
|
||||
err := httpd.RestoreFolders(dump.Folders, s.LoadDataFrom, s.LoadDataMode, s.LoadDataQuotaScan)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to restore folders from file %#v: %v", s.LoadDataFrom, err)
|
||||
}
|
||||
|
@ -302,5 +294,13 @@ func (s *Service) restoreDump(dump *dataprovider.BackupData) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("unable to restore users from file %#v: %v", s.LoadDataFrom, err)
|
||||
}
|
||||
err = httpd.RestoreAdmins(dump.Admins, s.LoadDataFrom, s.LoadDataMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to restore admins from file %#v: %v", s.LoadDataFrom, err)
|
||||
}
|
||||
err = httpd.RestoreAPIKeys(dump.APIKeys, s.LoadDataFrom, s.LoadDataMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to restore API keys from file %#v: %v", s.LoadDataFrom, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !noportable
|
||||
// +build !noportable
|
||||
|
||||
package service
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package service
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package sftpd
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package sftpd
|
||||
|
|
|
@ -406,7 +406,7 @@ func (c *Configuration) AcceptInboundConnection(conn net.Conn, config *ssh.Serve
|
|||
logger.Log(logger.LevelDebug, common.ProtocolSSH, connectionID,
|
||||
"User %#v, logged in with: %#v, from ip: %#v, client version %#v",
|
||||
user.Username, loginType, ipAddr, string(sconn.ClientVersion()))
|
||||
dataprovider.UpdateLastLogin(&user) //nolint:errcheck
|
||||
dataprovider.UpdateLastLogin(&user)
|
||||
|
||||
sshConnection := common.NewSSHConnection(connectionID, conn)
|
||||
common.Connections.AddSSHConnection(sshConnection)
|
||||
|
|
|
@ -43,7 +43,7 @@ func ServeSubSystemConnection(user *dataprovider.User, connectionID string, read
|
|||
logger.Warn(logSender, connectionID, "unable to check fs root: %v close fs error: %v", err, errClose)
|
||||
return err
|
||||
}
|
||||
dataprovider.UpdateLastLogin(user) //nolint:errcheck
|
||||
dataprovider.UpdateLastLogin(user)
|
||||
|
||||
connection := &Connection{
|
||||
BaseConnection: common.NewBaseConnection(connectionID, common.ProtocolSFTP, "", "", *user),
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !noazblob
|
||||
// +build !noazblob
|
||||
|
||||
package vfs
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build noazblob
|
||||
// +build noazblob
|
||||
|
||||
package vfs
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !nogcs
|
||||
// +build !nogcs
|
||||
|
||||
package vfs
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build nogcs
|
||||
// +build nogcs
|
||||
|
||||
package vfs
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !nos3
|
||||
// +build !nos3
|
||||
|
||||
package vfs
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build nos3
|
||||
// +build nos3
|
||||
|
||||
package vfs
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !darwin && !linux && !freebsd
|
||||
// +build !darwin,!linux,!freebsd
|
||||
|
||||
package vfs
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package vfs
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build freebsd || darwin
|
||||
// +build freebsd darwin
|
||||
|
||||
package vfs
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package vfs
|
||||
|
|
|
@ -1118,10 +1118,22 @@ func TestCachedUserWithFolders(t *testing.T) {
|
|||
|
||||
folder, err := dataprovider.GetFolderByName(folderName)
|
||||
assert.NoError(t, err)
|
||||
// updating a used folder should invalidate the cache
|
||||
// updating a used folder should invalidate the cache only if the fs changed
|
||||
err = dataprovider.UpdateFolder(&folder, folder.Users)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, isCached, _, loginMethod, err = server.authenticate(req, ipAddr)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, isCached)
|
||||
assert.Equal(t, dataprovider.LoginMethodPassword, loginMethod)
|
||||
cachedUser, ok = dataprovider.GetCachedWebDAVUser(username)
|
||||
if assert.True(t, ok) {
|
||||
assert.False(t, cachedUser.IsExpired())
|
||||
}
|
||||
// changing the folder path should invalidate the cache
|
||||
folder.MappedPath = filepath.Join(os.TempDir(), "anotherpath")
|
||||
err = dataprovider.UpdateFolder(&folder, folder.Users)
|
||||
assert.NoError(t, err)
|
||||
_, isCached, _, loginMethod, err = server.authenticate(req, ipAddr)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, isCached)
|
||||
|
|
|
@ -208,7 +208,7 @@ func (s *webDavServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
common.Connections.Add(connection)
|
||||
defer common.Connections.Remove(connection.GetID())
|
||||
|
||||
dataprovider.UpdateLastLogin(&user) //nolint:errcheck
|
||||
dataprovider.UpdateLastLogin(&user)
|
||||
|
||||
if s.checkRequestMethod(ctx, r, connection) {
|
||||
w.Header().Set("Content-Type", "text/xml; charset=utf-8")
|
||||
|
|
Loading…
Reference in a new issue