mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-21 23:20:24 +00:00
webdav: fix proppatch handling
also respect login delay for cached webdav users and check the home dir as soon as the user authenticates Fixes #239
This commit is contained in:
parent
4a88ea5c03
commit
034d89876d
7 changed files with 81 additions and 30 deletions
|
@ -582,7 +582,11 @@ func UpdateLastLogin(user User) error {
|
|||
lastLogin := utils.GetTimeFromMsecSinceEpoch(user.LastLogin)
|
||||
diff := -time.Until(lastLogin)
|
||||
if diff < 0 || diff > lastLoginMinDelay {
|
||||
return provider.updateLastLogin(user.Username)
|
||||
err := provider.updateLastLogin(user.Username)
|
||||
if err == nil {
|
||||
updateWebDavCachedUserLastLogin(user.Username)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -2132,6 +2136,15 @@ func updateVFoldersQuotaAfterRestore(foldersToScan []string) {
|
|||
}
|
||||
}
|
||||
|
||||
func updateWebDavCachedUserLastLogin(username string) {
|
||||
result, ok := webDAVUsersCache.Load(username)
|
||||
if ok {
|
||||
cachedUser := result.(*CachedUser)
|
||||
cachedUser.User.LastLogin = utils.GetTimeAsMsSinceEpoch(time.Now())
|
||||
webDAVUsersCache.Store(cachedUser.User.Username, cachedUser)
|
||||
}
|
||||
}
|
||||
|
||||
// CacheWebDAVUser add a user to the WebDAV cache
|
||||
func CacheWebDAVUser(cachedUser *CachedUser, maxSize int) {
|
||||
if maxSize > 0 {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Data At Rest Encryption (DARE)
|
||||
|
||||
SFTPGo supports data at-rest encryption via the `cryptfs` virtual file system, in this mode SFTPGo transparently encrypts and decrypts data (to/from the disk) on-the-fly during uploads and/or downloads, making sure that the files at-rest on the server-side are always encrypted.
|
||||
SFTPGo supports data at-rest encryption via its `cryptfs` virtual file system, in this mode SFTPGo transparently encrypts and decrypts data (to/from the disk) on-the-fly during uploads and/or downloads, making sure that the files at-rest on the server-side are always encrypted.
|
||||
|
||||
So, because of the way it works, as described here above, when you set up an encrypted filesystem for a user you need to make sure it points to an empty path/directory (that has no files in it). Otherwise, it would try to decrypt existing files that are not encrypted in the first place and fail.
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Key Management Services
|
||||
|
||||
SFTPGo stores sensitive data such as Cloud accounts credentials. This data are stored as ciphertext and only loaded to RAM in plaintext when needed.
|
||||
SFTPGo stores sensitive data such as Cloud accounts credentials or passphrases to derive per-object encryption keys. These data are stored as ciphertext and only loaded to RAM in plaintext when needed.
|
||||
|
||||
## Supported Services for encryption and decryption
|
||||
|
||||
|
|
|
@ -328,19 +328,14 @@ func (h *encryptedFileHeader) Store(f *os.File) error {
|
|||
}
|
||||
|
||||
func (h *encryptedFileHeader) Load(f *os.File) error {
|
||||
vers := make([]byte, 1)
|
||||
_, err := io.ReadFull(f, vers)
|
||||
header := make([]byte, 1+nonceV10Size)
|
||||
_, err := io.ReadFull(f, header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.version = vers[0]
|
||||
h.version = header[0]
|
||||
if h.version == version10 {
|
||||
nonce := make([]byte, nonceV10Size)
|
||||
_, err := io.ReadFull(f, nonce)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.nonce = nonce
|
||||
h.nonce = header[1:]
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unsupported encryption version: %v", h.version)
|
||||
|
|
|
@ -143,7 +143,8 @@ func (c *Connection) OpenFile(ctx context.Context, name string, flag int, perm o
|
|||
if err != nil {
|
||||
return nil, c.GetFsError(err)
|
||||
}
|
||||
if flag == os.O_RDONLY {
|
||||
|
||||
if flag == os.O_RDONLY || c.request.Method == "PROPPATCH" {
|
||||
// Download, Stat, Readdir or simply open/close
|
||||
return c.getFile(p, name)
|
||||
}
|
||||
|
|
|
@ -84,6 +84,20 @@ func (s *webDavServer) listenAndServe() error {
|
|||
return httpServer.ListenAndServe()
|
||||
}
|
||||
|
||||
func (s *webDavServer) checkRequestMethod(ctx context.Context, r *http.Request, connection *Connection, prefix string) {
|
||||
// see RFC4918, section 9.4
|
||||
if r.Method == http.MethodGet {
|
||||
p := strings.TrimPrefix(path.Clean(r.URL.Path), prefix)
|
||||
info, err := connection.Stat(ctx, p)
|
||||
if err == nil && info.IsDir() {
|
||||
r.Method = "PROPFIND"
|
||||
if r.Header.Get("Depth") == "" {
|
||||
r.Header.Add("Depth", "1")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP implements the http.Handler interface
|
||||
func (s *webDavServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
|
@ -97,7 +111,7 @@ func (s *webDavServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
http.Error(w, common.ErrConnectionDenied.Error(), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
user, isCached, lockSystem, err := s.authenticate(r)
|
||||
user, _, lockSystem, err := s.authenticate(r)
|
||||
if err != nil {
|
||||
w.Header().Set("WWW-Authenticate", "Basic realm=\"SFTPGo WebDAV\"")
|
||||
http.Error(w, err401.Error(), http.StatusUnauthorized)
|
||||
|
@ -135,24 +149,10 @@ func (s *webDavServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
common.Connections.Add(connection)
|
||||
defer common.Connections.Remove(connection.GetID())
|
||||
|
||||
if !isCached {
|
||||
// we check the home directory only if the user is not cached
|
||||
connection.Fs.CheckRootPath(connection.GetUsername(), user.GetUID(), user.GetGID())
|
||||
}
|
||||
dataprovider.UpdateLastLogin(user) //nolint:errcheck
|
||||
|
||||
prefix := path.Join("/", user.Username)
|
||||
// see RFC4918, section 9.4
|
||||
if r.Method == http.MethodGet {
|
||||
p := strings.TrimPrefix(path.Clean(r.URL.Path), prefix)
|
||||
info, err := connection.Stat(ctx, p)
|
||||
if err == nil && info.IsDir() {
|
||||
r.Method = "PROPFIND"
|
||||
if r.Header.Get("Depth") == "" {
|
||||
r.Header.Add("Depth", "1")
|
||||
}
|
||||
}
|
||||
}
|
||||
s.checkRequestMethod(ctx, r, connection, prefix)
|
||||
|
||||
handler := webdav.Handler{
|
||||
Prefix: prefix,
|
||||
|
@ -199,8 +199,12 @@ func (s *webDavServer) authenticate(r *http.Request) (dataprovider.User, bool, w
|
|||
cachedUser.Expiration = time.Now().Add(time.Duration(s.config.Cache.Users.ExpirationTime) * time.Minute)
|
||||
}
|
||||
dataprovider.CacheWebDAVUser(cachedUser, s.config.Cache.Users.MaxSize)
|
||||
tempFs, err := user.GetFilesystem("temp")
|
||||
if err == nil {
|
||||
tempFs.CheckRootPath(user.Username, user.UID, user.GID)
|
||||
}
|
||||
}
|
||||
return user, false, lockSystem, err
|
||||
return user, false, lockSystem, nil
|
||||
}
|
||||
|
||||
func (s *webDavServer) validateUser(user dataprovider.User, r *http.Request) (string, error) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package webdavd_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
@ -360,6 +361,43 @@ func TestBasicHandlingCryptFs(t *testing.T) {
|
|||
assert.Len(t, common.Connections.GetStats(), 0)
|
||||
}
|
||||
|
||||
func TestPropPatch(t *testing.T) {
|
||||
for _, u := range []dataprovider.User{getTestUser(), getTestUserWithCryptFs()} {
|
||||
user, _, err := httpd.AddUser(u, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
client := getWebDavClient(user)
|
||||
assert.NoError(t, checkBasicFunc(client))
|
||||
|
||||
testFilePath := filepath.Join(homeBasePath, testFileName)
|
||||
testFileSize := int64(65535)
|
||||
err = createTestFile(testFilePath, testFileSize)
|
||||
assert.NoError(t, err)
|
||||
err = uploadFile(testFilePath, testFileName, testFileSize, client)
|
||||
assert.NoError(t, err)
|
||||
httpClient := httpclient.GetHTTPClient()
|
||||
propatchBody := `<?xml version="1.0" encoding="utf-8" ?><D:propertyupdate xmlns:D="DAV:" xmlns:Z="urn:schemas-microsoft-com:"><D:set><D:prop><Z:Win32CreationTime>Wed, 04 Nov 2020 13:25:51 GMT</Z:Win32CreationTime><Z:Win32LastAccessTime>Sat, 05 Dec 2020 21:16:12 GMT</Z:Win32LastAccessTime><Z:Win32LastModifiedTime>Wed, 04 Nov 2020 13:25:51 GMT</Z:Win32LastModifiedTime><Z:Win32FileAttributes>00000000</Z:Win32FileAttributes></D:prop></D:set></D:propertyupdate>`
|
||||
req, err := http.NewRequest("PROPPATCH", fmt.Sprintf("http://%v/%v/%v", webDavServerAddr, user.Username, testFileName), bytes.NewReader([]byte(propatchBody)))
|
||||
assert.NoError(t, err)
|
||||
req.SetBasicAuth(u.Username, u.Password)
|
||||
resp, err := httpClient.Do(req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 207, resp.StatusCode)
|
||||
err = resp.Body.Close()
|
||||
assert.NoError(t, err)
|
||||
info, err := client.Stat(testFileName)
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, testFileSize, info.Size())
|
||||
}
|
||||
err = os.Remove(testFilePath)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpd.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, common.Connections.GetStats(), 0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoginInvalidPwd(t *testing.T) {
|
||||
u := getTestUser()
|
||||
user, _, err := httpd.AddUser(u, http.StatusOK)
|
||||
|
|
Loading…
Reference in a new issue