2023-01-03 09:18:30 +00:00
|
|
|
// Copyright (C) 2019-2023 Nicola Murino
|
2022-07-17 18:16:00 +00:00
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Affero General Public License as published
|
|
|
|
// by the Free Software Foundation, version 3.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU Affero General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Affero General Public License
|
2023-01-03 09:18:30 +00:00
|
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
2022-07-17 18:16:00 +00:00
|
|
|
|
2021-03-27 18:10:27 +00:00
|
|
|
package dataprovider
|
|
|
|
|
|
|
|
import (
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
2022-11-03 07:31:40 +00:00
|
|
|
"github.com/drakkan/webdav"
|
2021-03-27 18:10:27 +00:00
|
|
|
|
2022-07-24 14:18:54 +00:00
|
|
|
"github.com/drakkan/sftpgo/v2/internal/logger"
|
|
|
|
"github.com/drakkan/sftpgo/v2/internal/util"
|
2021-03-27 18:10:27 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
webDAVUsersCache *usersCache
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
webDAVUsersCache = &usersCache{
|
|
|
|
users: map[string]CachedUser{},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// InitializeWebDAVUserCache initializes the cache for webdav users
|
|
|
|
func InitializeWebDAVUserCache(maxSize int) {
|
|
|
|
webDAVUsersCache = &usersCache{
|
|
|
|
users: map[string]CachedUser{},
|
|
|
|
maxSize: maxSize,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// CachedUser adds fields useful for caching to a SFTPGo user
|
|
|
|
type CachedUser struct {
|
|
|
|
User User
|
|
|
|
Expiration time.Time
|
|
|
|
Password string
|
|
|
|
LockSystem webdav.LockSystem
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsExpired returns true if the cached user is expired
|
|
|
|
func (c *CachedUser) IsExpired() bool {
|
|
|
|
if c.Expiration.IsZero() {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return c.Expiration.Before(time.Now())
|
|
|
|
}
|
|
|
|
|
|
|
|
type usersCache struct {
|
|
|
|
sync.RWMutex
|
|
|
|
users map[string]CachedUser
|
|
|
|
maxSize int
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cache *usersCache) updateLastLogin(username string) {
|
|
|
|
cache.Lock()
|
|
|
|
defer cache.Unlock()
|
|
|
|
|
|
|
|
if cachedUser, ok := cache.users[username]; ok {
|
2021-07-11 13:26:51 +00:00
|
|
|
cachedUser.User.LastLogin = util.GetTimeAsMsSinceEpoch(time.Now())
|
2021-03-27 18:10:27 +00:00
|
|
|
cache.users[username] = cachedUser
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// swapWebDAVUser updates an existing cached user with the specified one
|
|
|
|
// preserving the lock fs if possible
|
2022-04-25 13:49:11 +00:00
|
|
|
// FIXME: this could be racy in rare cases
|
|
|
|
func (cache *usersCache) swap(userRef *User) {
|
|
|
|
user := userRef.getACopy()
|
|
|
|
err := user.LoadAndApplyGroupSettings()
|
|
|
|
|
2021-03-27 18:10:27 +00:00
|
|
|
cache.Lock()
|
|
|
|
defer cache.Unlock()
|
|
|
|
|
|
|
|
if cachedUser, ok := cache.users[user.Username]; ok {
|
|
|
|
if cachedUser.User.Password != user.Password {
|
2023-02-27 18:02:43 +00:00
|
|
|
providerLog(logger.LevelDebug, "current password different from the cached one for user %q, removing from cache",
|
2021-08-20 07:35:06 +00:00
|
|
|
user.Username)
|
2021-03-27 18:10:27 +00:00
|
|
|
// the password changed, the cached user is no longer valid
|
|
|
|
delete(cache.users, user.Username)
|
|
|
|
return
|
|
|
|
}
|
2022-04-25 13:49:11 +00:00
|
|
|
if err != nil {
|
2023-02-27 18:02:43 +00:00
|
|
|
providerLog(logger.LevelDebug, "unable to load group settings, for user %q, removing from cache, err :%v",
|
2022-04-25 13:49:11 +00:00
|
|
|
user.Username, err)
|
|
|
|
delete(cache.users, user.Username)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if cachedUser.User.isFsEqual(&user) {
|
2021-03-27 18:10:27 +00:00
|
|
|
// the updated user has the same fs as the cached one, we can preserve the lock filesystem
|
2023-02-27 18:02:43 +00:00
|
|
|
providerLog(logger.LevelDebug, "current password and fs unchanged for for user %q, swap cached one",
|
2021-08-20 07:35:06 +00:00
|
|
|
user.Username)
|
2022-04-25 13:49:11 +00:00
|
|
|
cachedUser.User = user
|
2021-03-27 18:10:27 +00:00
|
|
|
cache.users[user.Username] = cachedUser
|
|
|
|
} else {
|
|
|
|
// filesystem changed, the cached user is no longer valid
|
2023-02-27 18:02:43 +00:00
|
|
|
providerLog(logger.LevelDebug, "current fs different from the cached one for user %q, removing from cache",
|
2021-08-20 07:35:06 +00:00
|
|
|
user.Username)
|
2021-03-27 18:10:27 +00:00
|
|
|
delete(cache.users, user.Username)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cache *usersCache) add(cachedUser *CachedUser) {
|
|
|
|
cache.Lock()
|
|
|
|
defer cache.Unlock()
|
|
|
|
|
|
|
|
if cache.maxSize > 0 && len(cache.users) >= cache.maxSize {
|
|
|
|
var userToRemove string
|
|
|
|
var expirationTime time.Time
|
|
|
|
|
|
|
|
for k, v := range cache.users {
|
|
|
|
if userToRemove == "" {
|
|
|
|
userToRemove = k
|
|
|
|
expirationTime = v.Expiration
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
expireTime := v.Expiration
|
|
|
|
if !expireTime.IsZero() && expireTime.Before(expirationTime) {
|
|
|
|
userToRemove = k
|
|
|
|
expirationTime = expireTime
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
delete(cache.users, userToRemove)
|
|
|
|
}
|
|
|
|
|
|
|
|
if cachedUser.User.Username != "" {
|
|
|
|
cache.users[cachedUser.User.Username] = *cachedUser
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cache *usersCache) remove(username string) {
|
|
|
|
cache.Lock()
|
|
|
|
defer cache.Unlock()
|
|
|
|
|
|
|
|
delete(cache.users, username)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cache *usersCache) get(username string) (*CachedUser, bool) {
|
|
|
|
cache.RLock()
|
|
|
|
defer cache.RUnlock()
|
|
|
|
|
|
|
|
cachedUser, ok := cache.users[username]
|
|
|
|
return &cachedUser, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
// CacheWebDAVUser add a user to the WebDAV cache
|
|
|
|
func CacheWebDAVUser(cachedUser *CachedUser) {
|
|
|
|
webDAVUsersCache.add(cachedUser)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetCachedWebDAVUser returns a previously cached WebDAV user
|
|
|
|
func GetCachedWebDAVUser(username string) (*CachedUser, bool) {
|
|
|
|
return webDAVUsersCache.get(username)
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveCachedWebDAVUser removes a cached WebDAV user
|
|
|
|
func RemoveCachedWebDAVUser(username string) {
|
|
|
|
webDAVUsersCache.remove(username)
|
|
|
|
}
|