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:
Nicola Murino 2020-12-06 08:19:41 +01:00
parent 4a88ea5c03
commit 034d89876d
No known key found for this signature in database
GPG key ID: 2F1FB59433D5A8CB
7 changed files with 81 additions and 30 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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