package webdavd import ( "context" "crypto/tls" "errors" "fmt" "io" "io/ioutil" "net/http" "os" "path" "path/filepath" "runtime" "testing" "time" "github.com/eikenb/pipeat" "github.com/stretchr/testify/assert" "github.com/drakkan/sftpgo/common" "github.com/drakkan/sftpgo/dataprovider" "github.com/drakkan/sftpgo/httpd" "github.com/drakkan/sftpgo/vfs" ) const ( configDir = ".." testFile = "test_dav_file" ) var ( errWalkDir = errors.New("err walk dir") errWalkFile = errors.New("err walk file") ) // MockOsFs mockable OsFs type MockOsFs struct { vfs.Fs err error isAtomicUploadSupported bool } // Name returns the name for the Fs implementation func (fs MockOsFs) Name() string { return "mockOsFs" } // Open returns nil func (MockOsFs) Open(name string, offset int64) (*os.File, *pipeat.PipeReaderAt, func(), error) { return nil, nil, nil, nil } // IsUploadResumeSupported returns true if upload resume is supported func (MockOsFs) IsUploadResumeSupported() bool { return false } // IsAtomicUploadSupported returns true if atomic upload is supported func (fs MockOsFs) IsAtomicUploadSupported() bool { return fs.isAtomicUploadSupported } // Remove removes the named file or (empty) directory. func (fs MockOsFs) Remove(name string, isDir bool) error { if fs.err != nil { return fs.err } return os.Remove(name) } // Rename renames (moves) source to target func (fs MockOsFs) Rename(source, target string) error { if fs.err != nil { return fs.err } return os.Rename(source, target) } // Walk returns a duplicate path for testing func (fs MockOsFs) Walk(root string, walkFn filepath.WalkFunc) error { if fs.err == errWalkDir { walkFn("fsdpath", vfs.NewFileInfo("dpath", true, 0, time.Now(), false), nil) //nolint:errcheck walkFn("fsdpath", vfs.NewFileInfo("dpath", true, 0, time.Now(), false), nil) //nolint:errcheck return nil } walkFn("fsfpath", vfs.NewFileInfo("fpath", false, 0, time.Now(), false), nil) //nolint:errcheck return fs.err } // GetMimeType implements vfs.MimeTyper func (fs MockOsFs) GetMimeType(name string) (string, error) { return "application/octet-stream", nil } func newMockOsFs(err error, atomicUpload bool, connectionID, rootDir string) vfs.Fs { return &MockOsFs{ Fs: vfs.NewOsFs(connectionID, rootDir, nil), err: err, isAtomicUploadSupported: atomicUpload, } } func TestOrderDirsToRemove(t *testing.T) { user := dataprovider.User{} fs := vfs.NewOsFs("id", os.TempDir(), nil) connection := &Connection{ BaseConnection: common.NewBaseConnection(fs.ConnectionID(), common.ProtocolWebDAV, user, fs), request: nil, } dirsToRemove := []objectMapping{} orderedDirs := connection.orderDirsToRemove(dirsToRemove) assert.Equal(t, len(dirsToRemove), len(orderedDirs)) dirsToRemove = []objectMapping{ { fsPath: "dir1", virtualPath: "", }, } orderedDirs = connection.orderDirsToRemove(dirsToRemove) assert.Equal(t, len(dirsToRemove), len(orderedDirs)) dirsToRemove = []objectMapping{ { fsPath: "dir1", virtualPath: "", }, { fsPath: "dir12", virtualPath: "", }, { fsPath: filepath.Join("dir1", "a", "b"), virtualPath: "", }, { fsPath: filepath.Join("dir1", "a"), virtualPath: "", }, } orderedDirs = connection.orderDirsToRemove(dirsToRemove) if assert.Equal(t, len(dirsToRemove), len(orderedDirs)) { assert.Equal(t, "dir12", orderedDirs[0].fsPath) assert.Equal(t, filepath.Join("dir1", "a", "b"), orderedDirs[1].fsPath) assert.Equal(t, filepath.Join("dir1", "a"), orderedDirs[2].fsPath) assert.Equal(t, "dir1", orderedDirs[3].fsPath) } } func TestUserInvalidParams(t *testing.T) { u := dataprovider.User{ Username: "username", HomeDir: "invalid", } c := &Configuration{ BindPort: 9000, } server, err := newServer(c, configDir) assert.NoError(t, err) req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/%v", u.Username), nil) assert.NoError(t, err) _, err = server.validateUser(u, req) if assert.Error(t, err) { assert.EqualError(t, err, fmt.Sprintf("cannot login user with invalid home dir: %#v", u.HomeDir)) } u.HomeDir = filepath.Clean(os.TempDir()) subDir := "subdir" mappedPath1 := filepath.Join(os.TempDir(), "vdir1") vdirPath1 := "/vdir1" mappedPath2 := filepath.Join(os.TempDir(), "vdir1", subDir) vdirPath2 := "/vdir2" u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ BaseVirtualFolder: vfs.BaseVirtualFolder{ MappedPath: mappedPath1, }, VirtualPath: vdirPath1, }) u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{ BaseVirtualFolder: vfs.BaseVirtualFolder{ MappedPath: mappedPath2, }, VirtualPath: vdirPath2, }) _, err = server.validateUser(u, req) if assert.Error(t, err) { assert.EqualError(t, err, "overlapping mapped folders are allowed only with quota tracking disabled") } req.TLS = &tls.ConnectionState{} writeLog(req, nil) } func TestRemoteAddress(t *testing.T) { req, err := http.NewRequest(http.MethodGet, "/username", nil) assert.NoError(t, err) assert.Empty(t, req.RemoteAddr) remoteAddr1 := "100.100.100.100" remoteAddr2 := "172.172.172.172" req.Header.Set("X-Forwarded-For", remoteAddr1) checkRemoteAddress(req) assert.Equal(t, remoteAddr1, req.RemoteAddr) req.RemoteAddr = "" req.Header.Set("X-Forwarded-For", fmt.Sprintf("%v, %v", remoteAddr2, remoteAddr1)) checkRemoteAddress(req) assert.Equal(t, remoteAddr2, req.RemoteAddr) req.Header.Del("X-Forwarded-For") req.RemoteAddr = "" req.Header.Set("X-Real-IP", remoteAddr1) checkRemoteAddress(req) assert.Equal(t, remoteAddr1, req.RemoteAddr) req.RemoteAddr = "" oldValue := common.Config.ProxyProtocol common.Config.ProxyProtocol = 1 checkRemoteAddress(req) assert.Empty(t, req.RemoteAddr) common.Config.ProxyProtocol = oldValue } func TestConnWithNilRequest(t *testing.T) { c := &Connection{} assert.Empty(t, c.GetClientVersion()) assert.Empty(t, c.GetCommand()) assert.Empty(t, c.GetRemoteAddress()) } func TestResolvePathErrors(t *testing.T) { ctx := context.Background() user := dataprovider.User{ HomeDir: "invalid", } user.Permissions = make(map[string][]string) user.Permissions["/"] = []string{dataprovider.PermAny} fs := vfs.NewOsFs("connID", user.HomeDir, nil) connection := &Connection{ BaseConnection: common.NewBaseConnection(fs.ConnectionID(), common.ProtocolWebDAV, user, fs), } err := connection.Mkdir(ctx, "", os.ModePerm) if assert.Error(t, err) { assert.EqualError(t, err, common.ErrGenericFailure.Error()) } err = connection.Rename(ctx, "oldName", "newName") if assert.Error(t, err) { assert.EqualError(t, err, common.ErrGenericFailure.Error()) } _, err = connection.Stat(ctx, "name") if assert.Error(t, err) { assert.EqualError(t, err, common.ErrGenericFailure.Error()) } err = connection.RemoveAll(ctx, "") if assert.Error(t, err) { assert.EqualError(t, err, common.ErrGenericFailure.Error()) } _, err = connection.OpenFile(ctx, "", 0, os.ModePerm) if assert.Error(t, err) { assert.EqualError(t, err, common.ErrGenericFailure.Error()) } if runtime.GOOS != "windows" { connection.User.HomeDir = filepath.Clean(os.TempDir()) connection.Fs = vfs.NewOsFs("connID", connection.User.HomeDir, nil) subDir := "sub" testTxtFile := "file.txt" err = os.MkdirAll(filepath.Join(os.TempDir(), subDir, subDir), os.ModePerm) assert.NoError(t, err) err = ioutil.WriteFile(filepath.Join(os.TempDir(), subDir, subDir, testTxtFile), []byte("content"), os.ModePerm) assert.NoError(t, err) err = os.Chmod(filepath.Join(os.TempDir(), subDir, subDir), 0001) assert.NoError(t, err) err = connection.Rename(ctx, testTxtFile, path.Join(subDir, subDir, testTxtFile)) if assert.Error(t, err) { assert.EqualError(t, err, common.ErrPermissionDenied.Error()) } _, err = connection.putFile(filepath.Join(connection.User.HomeDir, subDir, subDir, testTxtFile), path.Join(subDir, subDir, testTxtFile)) if assert.Error(t, err) { assert.EqualError(t, err, common.ErrPermissionDenied.Error()) } err = os.Chmod(filepath.Join(os.TempDir(), subDir, subDir), os.ModePerm) assert.NoError(t, err) err = os.RemoveAll(filepath.Join(os.TempDir(), subDir)) assert.NoError(t, err) } } func TestFileAccessErrors(t *testing.T) { ctx := context.Background() user := dataprovider.User{ HomeDir: filepath.Clean(os.TempDir()), } user.Permissions = make(map[string][]string) user.Permissions["/"] = []string{dataprovider.PermAny} fs := vfs.NewOsFs("connID", user.HomeDir, nil) connection := &Connection{ BaseConnection: common.NewBaseConnection(fs.ConnectionID(), common.ProtocolWebDAV, user, fs), } missingPath := "missing path" fsMissingPath := filepath.Join(user.HomeDir, missingPath) err := connection.RemoveAll(ctx, missingPath) if assert.Error(t, err) { assert.EqualError(t, err, os.ErrNotExist.Error()) } info := vfs.NewFileInfo(missingPath, true, 0, time.Now(), false) _, err = connection.getFile(fsMissingPath, missingPath, info) if assert.Error(t, err) { assert.EqualError(t, err, os.ErrNotExist.Error()) } info = vfs.NewFileInfo(missingPath, false, 123, time.Now(), false) _, err = connection.getFile(fsMissingPath, missingPath, info) if assert.Error(t, err) { assert.EqualError(t, err, os.ErrNotExist.Error()) } p := filepath.Join(user.HomeDir, "adir", missingPath) _, err = connection.handleUploadToNewFile(p, p, path.Join("adir", missingPath)) if assert.Error(t, err) { assert.EqualError(t, err, os.ErrNotExist.Error()) } _, err = connection.handleUploadToExistingFile(p, p, 0, path.Join("adir", missingPath)) if assert.Error(t, err) { assert.EqualError(t, err, os.ErrNotExist.Error()) } connection.Fs = newMockOsFs(nil, false, fs.ConnectionID(), user.HomeDir) _, err = connection.handleUploadToExistingFile(p, p, 0, path.Join("adir", missingPath)) if assert.Error(t, err) { assert.EqualError(t, err, os.ErrNotExist.Error()) } f, err := ioutil.TempFile("", "temp") assert.NoError(t, err) err = f.Close() assert.NoError(t, err) davFile, err := connection.handleUploadToExistingFile(f.Name(), f.Name(), 123, f.Name()) if assert.NoError(t, err) { transfer := davFile.(*webDavFile) transfers := connection.GetTransfers() if assert.Equal(t, 1, len(transfers)) { assert.Equal(t, transfers[0].ID, transfer.GetID()) assert.Equal(t, int64(123), transfer.InitialSize) err = transfer.Close() assert.NoError(t, err) assert.Equal(t, 0, len(connection.GetTransfers())) } } err = os.Remove(f.Name()) assert.NoError(t, err) } func TestRemoveDirTree(t *testing.T) { user := dataprovider.User{ HomeDir: filepath.Clean(os.TempDir()), } user.Permissions = make(map[string][]string) user.Permissions["/"] = []string{dataprovider.PermAny} fs := vfs.NewOsFs("connID", user.HomeDir, nil) connection := &Connection{ BaseConnection: common.NewBaseConnection(fs.ConnectionID(), common.ProtocolWebDAV, user, fs), } vpath := path.Join("adir", "missing") p := filepath.Join(user.HomeDir, "adir", "missing") err := connection.removeDirTree(p, vpath) if assert.Error(t, err) { assert.True(t, os.IsNotExist(err)) } connection.Fs = newMockOsFs(nil, false, "mockID", user.HomeDir) err = connection.removeDirTree(p, vpath) if assert.Error(t, err) { assert.True(t, os.IsNotExist(err)) } errFake := errors.New("fake err") connection.Fs = newMockOsFs(errFake, false, "mockID", user.HomeDir) err = connection.removeDirTree(p, vpath) if assert.Error(t, err) { assert.EqualError(t, err, errFake.Error()) } connection.Fs = newMockOsFs(errWalkDir, true, "mockID", user.HomeDir) err = connection.removeDirTree(p, vpath) if assert.Error(t, err) { assert.True(t, os.IsNotExist(err)) } connection.Fs = newMockOsFs(errWalkFile, false, "mockID", user.HomeDir) err = connection.removeDirTree(p, vpath) if assert.Error(t, err) { assert.EqualError(t, err, errWalkFile.Error()) } connection.User.Permissions["/"] = []string{dataprovider.PermListItems} connection.Fs = newMockOsFs(nil, false, "mockID", user.HomeDir) err = connection.removeDirTree(p, vpath) if assert.Error(t, err) { assert.EqualError(t, err, common.ErrPermissionDenied.Error()) } } func TestContentType(t *testing.T) { user := dataprovider.User{ HomeDir: filepath.Clean(os.TempDir()), } user.Permissions = make(map[string][]string) user.Permissions["/"] = []string{dataprovider.PermAny} fs := vfs.NewOsFs("connID", user.HomeDir, nil) connection := &Connection{ BaseConnection: common.NewBaseConnection(fs.ConnectionID(), common.ProtocolWebDAV, user, fs), } testFilePath := filepath.Join(user.HomeDir, testFile) ctx := context.Background() baseTransfer := common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFile, common.TransferDownload, 0, 0, 0, false, fs) info := vfs.NewFileInfo(testFilePath, true, 0, time.Now(), false) davFile := newWebDavFile(baseTransfer, nil, nil, info) fi, err := davFile.Stat() if assert.NoError(t, err) { ctype, err := fi.(webDavFileInfo).ContentType(ctx) assert.NoError(t, err) assert.Equal(t, "inode/directory", ctype) } err = davFile.Close() assert.NoError(t, err) fs = newMockOsFs(nil, false, fs.ConnectionID(), user.GetHomeDir()) err = ioutil.WriteFile(testFilePath, []byte(""), os.ModePerm) assert.NoError(t, err) fi, err = os.Stat(testFilePath) assert.NoError(t, err) davFile = newWebDavFile(baseTransfer, nil, nil, fi) davFile.Fs = fs fi, err = davFile.Stat() if assert.NoError(t, err) { ctype, err := fi.(webDavFileInfo).ContentType(ctx) assert.NoError(t, err) assert.Equal(t, "application/octet-stream", ctype) } _, err = davFile.Readdir(-1) assert.Error(t, err) err = davFile.Close() assert.NoError(t, err) err = os.Remove(testFilePath) assert.NoError(t, err) } func TestTransferReadWriteErrors(t *testing.T) { user := dataprovider.User{ HomeDir: filepath.Clean(os.TempDir()), } user.Permissions = make(map[string][]string) user.Permissions["/"] = []string{dataprovider.PermAny} fs := vfs.NewOsFs("connID", user.HomeDir, nil) connection := &Connection{ BaseConnection: common.NewBaseConnection(fs.ConnectionID(), common.ProtocolWebDAV, user, fs), } testFilePath := filepath.Join(user.HomeDir, testFile) baseTransfer := common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFile, common.TransferUpload, 0, 0, 0, false, fs) davFile := newWebDavFile(baseTransfer, nil, nil, nil) assert.False(t, davFile.isDir()) p := make([]byte, 1) _, err := davFile.Read(p) assert.EqualError(t, err, common.ErrOpUnsupported.Error()) r, w, err := pipeat.Pipe() assert.NoError(t, err) davFile = newWebDavFile(baseTransfer, nil, r, nil) davFile.Connection.RemoveTransfer(davFile.BaseTransfer) davFile = newWebDavFile(baseTransfer, vfs.NewPipeWriter(w), nil, nil) davFile.Connection.RemoveTransfer(davFile.BaseTransfer) err = r.Close() assert.NoError(t, err) err = w.Close() assert.NoError(t, err) baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFile, common.TransferDownload, 0, 0, 0, false, fs) davFile = newWebDavFile(baseTransfer, nil, nil, nil) _, err = davFile.Read(p) assert.True(t, os.IsNotExist(err)) _, err = davFile.Stat() assert.True(t, os.IsNotExist(err)) baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFile, common.TransferDownload, 0, 0, 0, false, fs) err = ioutil.WriteFile(testFilePath, []byte(""), os.ModePerm) assert.NoError(t, err) f, err := os.Open(testFilePath) if assert.NoError(t, err) { err = f.Close() assert.NoError(t, err) } davFile = newWebDavFile(baseTransfer, nil, nil, nil) davFile.reader = f err = davFile.Close() assert.EqualError(t, err, common.ErrGenericFailure.Error()) err = davFile.Close() assert.EqualError(t, err, common.ErrTransferClosed.Error()) _, err = davFile.Read(p) assert.Error(t, err) info, err := davFile.Stat() if assert.NoError(t, err) { assert.Equal(t, int64(0), info.Size()) } baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFile, common.TransferDownload, 0, 0, 0, false, fs) davFile = newWebDavFile(baseTransfer, nil, nil, nil) davFile.writer = f err = davFile.Close() assert.EqualError(t, err, common.ErrGenericFailure.Error()) err = os.Remove(testFilePath) assert.NoError(t, err) } func TestTransferSeek(t *testing.T) { user := dataprovider.User{ HomeDir: filepath.Clean(os.TempDir()), } user.Permissions = make(map[string][]string) user.Permissions["/"] = []string{dataprovider.PermAny} fs := vfs.NewOsFs("connID", user.HomeDir, nil) connection := &Connection{ BaseConnection: common.NewBaseConnection(fs.ConnectionID(), common.ProtocolWebDAV, user, fs), } testFilePath := filepath.Join(user.HomeDir, testFile) baseTransfer := common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFile, common.TransferUpload, 0, 0, 0, false, fs) davFile := newWebDavFile(baseTransfer, nil, nil, nil) _, err := davFile.Seek(0, io.SeekStart) assert.EqualError(t, err, common.ErrOpUnsupported.Error()) err = davFile.Close() assert.NoError(t, err) baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFile, common.TransferDownload, 0, 0, 0, false, fs) davFile = newWebDavFile(baseTransfer, nil, nil, nil) _, err = davFile.Seek(0, io.SeekCurrent) assert.True(t, os.IsNotExist(err)) davFile.Connection.RemoveTransfer(davFile.BaseTransfer) err = ioutil.WriteFile(testFilePath, []byte("content"), os.ModePerm) assert.NoError(t, err) f, err := os.Open(testFilePath) if assert.NoError(t, err) { err = f.Close() assert.NoError(t, err) } baseTransfer = common.NewBaseTransfer(f, connection.BaseConnection, nil, testFilePath, testFile, common.TransferDownload, 0, 0, 0, false, fs) davFile = newWebDavFile(baseTransfer, nil, nil, nil) _, err = davFile.Seek(0, io.SeekStart) assert.Error(t, err) davFile.Connection.RemoveTransfer(davFile.BaseTransfer) baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testFilePath, testFile, common.TransferDownload, 0, 0, 0, false, fs) davFile = newWebDavFile(baseTransfer, nil, nil, nil) davFile.reader = f res, err := davFile.Seek(0, io.SeekStart) assert.NoError(t, err) assert.Equal(t, int64(0), res) davFile.Connection.RemoveTransfer(davFile.BaseTransfer) info, err := os.Stat(testFilePath) assert.NoError(t, err) davFile = newWebDavFile(baseTransfer, nil, nil, info) davFile.reader = f res, err = davFile.Seek(0, io.SeekEnd) assert.NoError(t, err) assert.Equal(t, int64(7), res) davFile = newWebDavFile(baseTransfer, nil, nil, info) davFile.reader = f davFile.Fs = newMockOsFs(nil, true, fs.ConnectionID(), user.GetHomeDir()) res, err = davFile.Seek(2, io.SeekStart) assert.NoError(t, err) assert.Equal(t, int64(2), res) davFile = newWebDavFile(baseTransfer, nil, nil, info) davFile.Fs = newMockOsFs(nil, true, fs.ConnectionID(), user.GetHomeDir()) res, err = davFile.Seek(2, io.SeekEnd) assert.NoError(t, err) assert.Equal(t, int64(5), res) davFile = newWebDavFile(baseTransfer, nil, nil, nil) res, err = davFile.Seek(2, io.SeekEnd) assert.EqualError(t, err, "unable to get file size, seek from end not possible") assert.Equal(t, int64(0), res) assert.Len(t, common.Connections.GetStats(), 0) 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) }