WebDAV: add caching for authenticated users

In this way we get a big performance boost
This commit is contained in:
Nicola Murino
2020-08-31 19:25:17 +02:00
parent f978355520
commit dbed110d02
15 changed files with 465 additions and 34 deletions

View File

@@ -20,6 +20,7 @@ import (
"github.com/drakkan/sftpgo/common"
"github.com/drakkan/sftpgo/dataprovider"
"github.com/drakkan/sftpgo/httpd"
"github.com/drakkan/sftpgo/vfs"
)
@@ -611,3 +612,263 @@ func TestTransferSeek(t *testing.T) {
err = os.Remove(testFilePath)
assert.NoError(t, err)
}
func TestBasicUsersCache(t *testing.T) {
username := "webdav_internal_test"
password := "pwd"
u := dataprovider.User{
Username: username,
Password: password,
HomeDir: filepath.Join(os.TempDir(), username),
Status: 1,
ExpirationDate: 0,
}
u.Permissions = make(map[string][]string)
u.Permissions["/"] = []string{dataprovider.PermAny}
user, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
c := &Configuration{
BindPort: 9000,
Cache: Cache{
Enabled: true,
MaxSize: 50,
ExpirationTime: 1,
},
}
server, err := newServer(c, configDir)
assert.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/%v", user.Username), nil)
assert.NoError(t, err)
_, _, err = server.authenticate(req)
assert.Error(t, err)
now := time.Now()
req.SetBasicAuth(username, password)
_, isCached, err := server.authenticate(req)
assert.NoError(t, err)
assert.False(t, isCached)
// now the user should be cached
var cachedUser dataprovider.CachedUser
result, ok := dataprovider.GetCachedWebDAVUser(username)
if assert.True(t, ok) {
cachedUser = result.(dataprovider.CachedUser)
assert.False(t, cachedUser.IsExpired())
assert.True(t, cachedUser.Expiration.After(now.Add(time.Duration(c.Cache.ExpirationTime)*time.Minute)))
// authenticate must return the cached user now
authUser, isCached, err := server.authenticate(req)
assert.NoError(t, err)
assert.True(t, isCached)
assert.Equal(t, cachedUser.User, authUser)
}
// a wrong password must fail
req.SetBasicAuth(username, "wrong")
_, _, err = server.authenticate(req)
assert.EqualError(t, err, dataprovider.ErrInvalidCredentials.Error())
req.SetBasicAuth(username, password)
// force cached user expiration
cachedUser.Expiration = now
dataprovider.CacheWebDAVUser(cachedUser, c.Cache.MaxSize)
result, ok = dataprovider.GetCachedWebDAVUser(username)
if assert.True(t, ok) {
cachedUser = result.(dataprovider.CachedUser)
assert.True(t, cachedUser.IsExpired())
}
// now authenticate should get the user from the data provider and update the cache
_, isCached, err = server.authenticate(req)
assert.NoError(t, err)
assert.False(t, isCached)
result, ok = dataprovider.GetCachedWebDAVUser(username)
if assert.True(t, ok) {
cachedUser = result.(dataprovider.CachedUser)
assert.False(t, cachedUser.IsExpired())
}
// cache is invalidated after a user modification
user, _, err = httpd.UpdateUser(user, http.StatusOK)
assert.NoError(t, err)
_, ok = dataprovider.GetCachedWebDAVUser(username)
assert.False(t, ok)
_, isCached, err = server.authenticate(req)
assert.NoError(t, err)
assert.False(t, isCached)
_, ok = dataprovider.GetCachedWebDAVUser(username)
assert.True(t, ok)
// cache is invalidated after user deletion
_, err = httpd.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
_, ok = dataprovider.GetCachedWebDAVUser(username)
assert.False(t, ok)
}
func TestUsersCacheSizeAndExpiration(t *testing.T) {
username := "webdav_internal_test"
password := "pwd"
u := dataprovider.User{
HomeDir: filepath.Join(os.TempDir(), username),
Status: 1,
ExpirationDate: 0,
}
u.Username = username + "1"
u.Password = password + "1"
u.Permissions = make(map[string][]string)
u.Permissions["/"] = []string{dataprovider.PermAny}
user1, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
u.Username = username + "2"
u.Password = password + "2"
user2, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
u.Username = username + "3"
u.Password = password + "3"
user3, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
u.Username = username + "4"
u.Password = password + "4"
user4, _, err := httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err)
c := &Configuration{
BindPort: 9000,
Cache: Cache{
Enabled: true,
MaxSize: 3,
ExpirationTime: 1,
},
}
server, err := newServer(c, configDir)
assert.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/%v", user1.Username), nil)
assert.NoError(t, err)
req.SetBasicAuth(user1.Username, password+"1")
_, isCached, err := server.authenticate(req)
assert.NoError(t, err)
assert.False(t, isCached)
req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("/%v", user2.Username), nil)
assert.NoError(t, err)
req.SetBasicAuth(user2.Username, password+"2")
_, isCached, err = server.authenticate(req)
assert.NoError(t, err)
assert.False(t, isCached)
req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("/%v", user3.Username), nil)
assert.NoError(t, err)
req.SetBasicAuth(user3.Username, password+"3")
_, isCached, err = server.authenticate(req)
assert.NoError(t, err)
assert.False(t, isCached)
// the first 3 users are now cached
_, ok := dataprovider.GetCachedWebDAVUser(user1.Username)
assert.True(t, ok)
_, ok = dataprovider.GetCachedWebDAVUser(user2.Username)
assert.True(t, ok)
_, ok = dataprovider.GetCachedWebDAVUser(user3.Username)
assert.True(t, ok)
req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("/%v", user4.Username), nil)
assert.NoError(t, err)
req.SetBasicAuth(user4.Username, password+"4")
_, isCached, err = server.authenticate(req)
assert.NoError(t, err)
assert.False(t, isCached)
// user1, the first cached, should be removed now
_, ok = dataprovider.GetCachedWebDAVUser(user1.Username)
assert.False(t, ok)
_, ok = dataprovider.GetCachedWebDAVUser(user2.Username)
assert.True(t, ok)
_, ok = dataprovider.GetCachedWebDAVUser(user3.Username)
assert.True(t, ok)
_, ok = dataprovider.GetCachedWebDAVUser(user4.Username)
assert.True(t, ok)
// user1 logins, user2 should be removed
req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("/%v", user1.Username), nil)
assert.NoError(t, err)
req.SetBasicAuth(user1.Username, password+"1")
_, isCached, err = server.authenticate(req)
assert.NoError(t, err)
assert.False(t, isCached)
_, ok = dataprovider.GetCachedWebDAVUser(user2.Username)
assert.False(t, ok)
_, ok = dataprovider.GetCachedWebDAVUser(user1.Username)
assert.True(t, ok)
_, ok = dataprovider.GetCachedWebDAVUser(user3.Username)
assert.True(t, ok)
_, ok = dataprovider.GetCachedWebDAVUser(user4.Username)
assert.True(t, ok)
// user2 logins, user3 should be removed
req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("/%v", user2.Username), nil)
assert.NoError(t, err)
req.SetBasicAuth(user2.Username, password+"2")
_, isCached, err = server.authenticate(req)
assert.NoError(t, err)
assert.False(t, isCached)
_, ok = dataprovider.GetCachedWebDAVUser(user3.Username)
assert.False(t, ok)
_, ok = dataprovider.GetCachedWebDAVUser(user1.Username)
assert.True(t, ok)
_, ok = dataprovider.GetCachedWebDAVUser(user2.Username)
assert.True(t, ok)
_, ok = dataprovider.GetCachedWebDAVUser(user4.Username)
assert.True(t, ok)
// user3 logins, user4 should be removed
req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("/%v", user3.Username), nil)
assert.NoError(t, err)
req.SetBasicAuth(user3.Username, password+"3")
_, isCached, err = server.authenticate(req)
assert.NoError(t, err)
assert.False(t, isCached)
_, ok = dataprovider.GetCachedWebDAVUser(user4.Username)
assert.False(t, ok)
_, ok = dataprovider.GetCachedWebDAVUser(user1.Username)
assert.True(t, ok)
_, ok = dataprovider.GetCachedWebDAVUser(user2.Username)
assert.True(t, ok)
_, ok = dataprovider.GetCachedWebDAVUser(user3.Username)
assert.True(t, ok)
// now remove user1 after an update
user1, _, err = httpd.UpdateUser(user1, http.StatusOK)
assert.NoError(t, err)
_, ok = dataprovider.GetCachedWebDAVUser(user1.Username)
assert.False(t, ok)
req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("/%v", user4.Username), nil)
assert.NoError(t, err)
req.SetBasicAuth(user4.Username, password+"4")
_, isCached, err = server.authenticate(req)
assert.NoError(t, err)
assert.False(t, isCached)
req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("/%v", user1.Username), nil)
assert.NoError(t, err)
req.SetBasicAuth(user1.Username, password+"1")
_, isCached, err = server.authenticate(req)
assert.NoError(t, err)
assert.False(t, isCached)
_, ok = dataprovider.GetCachedWebDAVUser(user2.Username)
assert.False(t, ok)
_, ok = dataprovider.GetCachedWebDAVUser(user1.Username)
assert.True(t, ok)
_, ok = dataprovider.GetCachedWebDAVUser(user3.Username)
assert.True(t, ok)
_, ok = dataprovider.GetCachedWebDAVUser(user4.Username)
assert.True(t, ok)
_, err = httpd.RemoveUser(user1, http.StatusOK)
assert.NoError(t, err)
_, err = httpd.RemoveUser(user2, http.StatusOK)
assert.NoError(t, err)
_, err = httpd.RemoveUser(user3, http.StatusOK)
assert.NoError(t, err)
_, err = httpd.RemoveUser(user4, http.StatusOK)
assert.NoError(t, err)
}