1package common_test
2
3import (
4	"bytes"
5	"crypto/rand"
6	"fmt"
7	"io"
8	"math"
9	"net"
10	"net/http"
11	"os"
12	"path"
13	"path/filepath"
14	"runtime"
15	"strings"
16	"testing"
17	"time"
18
19	_ "github.com/go-sql-driver/mysql"
20	_ "github.com/lib/pq"
21	_ "github.com/mattn/go-sqlite3"
22	"github.com/mhale/smtpd"
23	"github.com/pkg/sftp"
24	"github.com/pquerna/otp"
25	"github.com/pquerna/otp/totp"
26	"github.com/rs/zerolog"
27	"github.com/stretchr/testify/assert"
28	"github.com/stretchr/testify/require"
29	"golang.org/x/crypto/ssh"
30
31	"github.com/drakkan/sftpgo/v2/common"
32	"github.com/drakkan/sftpgo/v2/config"
33	"github.com/drakkan/sftpgo/v2/dataprovider"
34	"github.com/drakkan/sftpgo/v2/httpclient"
35	"github.com/drakkan/sftpgo/v2/httpdtest"
36	"github.com/drakkan/sftpgo/v2/kms"
37	"github.com/drakkan/sftpgo/v2/logger"
38	"github.com/drakkan/sftpgo/v2/mfa"
39	"github.com/drakkan/sftpgo/v2/sdk"
40	"github.com/drakkan/sftpgo/v2/vfs"
41)
42
43const (
44	configDir           = ".."
45	httpAddr            = "127.0.0.1:9999"
46	httpProxyAddr       = "127.0.0.1:7777"
47	sftpServerAddr      = "127.0.0.1:4022"
48	smtpServerAddr      = "127.0.0.1:2525"
49	defaultUsername     = "test_common_sftp"
50	defaultPassword     = "test_password"
51	defaultSFTPUsername = "test_common_sftpfs_user"
52	osWindows           = "windows"
53	testFileName        = "test_file_common_sftp.dat"
54	testDir             = "test_dir_common"
55)
56
57var (
58	allPerms        = []string{dataprovider.PermAny}
59	homeBasePath    string
60	testFileContent = []byte("test data")
61)
62
63func TestMain(m *testing.M) {
64	homeBasePath = os.TempDir()
65	logFilePath := filepath.Join(configDir, "common_test.log")
66	logger.InitLogger(logFilePath, 5, 1, 28, false, false, zerolog.DebugLevel)
67
68	os.Setenv("SFTPGO_DATA_PROVIDER__CREATE_DEFAULT_ADMIN", "1")
69	os.Setenv("SFTPGO_DEFAULT_ADMIN_USERNAME", "admin")
70	os.Setenv("SFTPGO_DEFAULT_ADMIN_PASSWORD", "password")
71	err := config.LoadConfig(configDir, "")
72	if err != nil {
73		logger.ErrorToConsole("error loading configuration: %v", err)
74		os.Exit(1)
75	}
76	providerConf := config.GetProviderConf()
77	logger.InfoToConsole("Starting COMMON tests, provider: %v", providerConf.Driver)
78
79	err = common.Initialize(config.GetCommonConfig())
80	if err != nil {
81		logger.WarnToConsole("error initializing common: %v", err)
82		os.Exit(1)
83	}
84
85	err = dataprovider.Initialize(providerConf, configDir, true)
86	if err != nil {
87		logger.ErrorToConsole("error initializing data provider: %v", err)
88		os.Exit(1)
89	}
90
91	httpConfig := config.GetHTTPConfig()
92	httpConfig.Timeout = 5
93	httpConfig.RetryMax = 0
94	httpConfig.Initialize(configDir) //nolint:errcheck
95	kmsConfig := config.GetKMSConfig()
96	err = kmsConfig.Initialize()
97	if err != nil {
98		logger.ErrorToConsole("error initializing kms: %v", err)
99		os.Exit(1)
100	}
101	mfaConfig := config.GetMFAConfig()
102	err = mfaConfig.Initialize()
103	if err != nil {
104		logger.ErrorToConsole("error initializing MFA: %v", err)
105		os.Exit(1)
106	}
107
108	sftpdConf := config.GetSFTPDConfig()
109	sftpdConf.Bindings[0].Port = 4022
110	sftpdConf.KeyboardInteractiveAuthentication = true
111
112	httpdConf := config.GetHTTPDConfig()
113	httpdConf.Bindings[0].Port = 4080
114	httpdtest.SetBaseURL("http://127.0.0.1:4080")
115
116	go func() {
117		if err := sftpdConf.Initialize(configDir); err != nil {
118			logger.ErrorToConsole("could not start SFTP server: %v", err)
119			os.Exit(1)
120		}
121	}()
122
123	go func() {
124		if err := httpdConf.Initialize(configDir); err != nil {
125			logger.ErrorToConsole("could not start HTTP server: %v", err)
126			os.Exit(1)
127		}
128	}()
129
130	go func() {
131		// start a test HTTP server to receive action notifications
132		http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
133			fmt.Fprintf(w, "OK\n")
134		})
135		http.HandleFunc("/404", func(w http.ResponseWriter, r *http.Request) {
136			w.WriteHeader(http.StatusNotFound)
137			fmt.Fprintf(w, "Not found\n")
138		})
139		if err := http.ListenAndServe(httpAddr, nil); err != nil {
140			logger.ErrorToConsole("could not start HTTP notification server: %v", err)
141			os.Exit(1)
142		}
143	}()
144
145	go func() {
146		common.Config.ProxyProtocol = 2
147		listener, err := net.Listen("tcp", httpProxyAddr)
148		if err != nil {
149			logger.ErrorToConsole("error creating listener for proxy protocol server: %v", err)
150			os.Exit(1)
151		}
152		proxyListener, err := common.Config.GetProxyListener(listener)
153		if err != nil {
154			logger.ErrorToConsole("error creating proxy protocol listener: %v", err)
155			os.Exit(1)
156		}
157		common.Config.ProxyProtocol = 0
158
159		s := &http.Server{}
160		if err := s.Serve(proxyListener); err != nil {
161			logger.ErrorToConsole("could not start HTTP proxy protocol server: %v", err)
162			os.Exit(1)
163		}
164	}()
165
166	go func() {
167		if err := smtpd.ListenAndServe(smtpServerAddr, func(remoteAddr net.Addr, from string, to []string, data []byte) error {
168			return nil
169		}, "SFTPGo test", "localhost"); err != nil {
170			logger.ErrorToConsole("could not start SMTP server: %v", err)
171			os.Exit(1)
172		}
173	}()
174
175	waitTCPListening(httpAddr)
176	waitTCPListening(httpProxyAddr)
177	waitTCPListening(smtpServerAddr)
178
179	waitTCPListening(sftpdConf.Bindings[0].GetAddress())
180	waitTCPListening(httpdConf.Bindings[0].GetAddress())
181
182	exitCode := m.Run()
183	os.Remove(logFilePath)
184	os.Exit(exitCode)
185}
186
187func TestBaseConnection(t *testing.T) {
188	u := getTestUser()
189	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
190	assert.NoError(t, err)
191	err = os.RemoveAll(user.GetHomeDir())
192	assert.NoError(t, err)
193	conn, client, err := getSftpClient(user)
194	if assert.NoError(t, err) {
195		defer conn.Close()
196		defer client.Close()
197		assert.NoError(t, checkBasicSFTP(client))
198		_, err = client.ReadDir(testDir)
199		assert.ErrorIs(t, err, os.ErrNotExist)
200		err = client.RemoveDirectory(testDir)
201		assert.ErrorIs(t, err, os.ErrNotExist)
202		err = client.Mkdir(testDir)
203		assert.NoError(t, err)
204		err = client.Mkdir(testDir)
205		assert.Error(t, err)
206		info, err := client.Stat(testDir)
207		if assert.NoError(t, err) {
208			assert.True(t, info.IsDir())
209		}
210		err = client.RemoveDirectory(testDir)
211		assert.NoError(t, err)
212		err = client.Remove(testFileName)
213		assert.ErrorIs(t, err, os.ErrNotExist)
214		f, err := client.Create(testFileName)
215		assert.NoError(t, err)
216		_, err = f.Write(testFileContent)
217		assert.NoError(t, err)
218		err = f.Close()
219		assert.NoError(t, err)
220		linkName := testFileName + ".link"
221		err = client.Symlink(testFileName, linkName)
222		assert.NoError(t, err)
223		err = client.Symlink(testFileName, testFileName)
224		assert.Error(t, err)
225		info, err = client.Stat(testFileName)
226		if assert.NoError(t, err) {
227			assert.Equal(t, int64(len(testFileContent)), info.Size())
228			assert.False(t, info.IsDir())
229		}
230		info, err = client.Lstat(linkName)
231		if assert.NoError(t, err) {
232			assert.NotEqual(t, int64(7), info.Size())
233			assert.True(t, info.Mode()&os.ModeSymlink != 0)
234			assert.False(t, info.IsDir())
235		}
236		err = client.RemoveDirectory(linkName)
237		if assert.Error(t, err) {
238			assert.Contains(t, err.Error(), "SSH_FX_FAILURE")
239		}
240		err = client.Remove(testFileName)
241		assert.NoError(t, err)
242		err = client.Remove(linkName)
243		assert.NoError(t, err)
244		err = client.Rename(testFileName, "test")
245		assert.ErrorIs(t, err, os.ErrNotExist)
246		f, err = client.Create(testFileName)
247		assert.NoError(t, err)
248		err = f.Close()
249		assert.NoError(t, err)
250		err = client.Rename(testFileName, testFileName+"1")
251		assert.NoError(t, err)
252		err = client.Remove(testFileName + "1")
253		assert.NoError(t, err)
254		err = client.RemoveDirectory("missing")
255		assert.Error(t, err)
256	}
257
258	_, err = httpdtest.RemoveUser(user, http.StatusOK)
259	assert.NoError(t, err)
260	err = os.RemoveAll(user.GetHomeDir())
261	assert.NoError(t, err)
262}
263
264func TestSetStat(t *testing.T) {
265	u := getTestUser()
266	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
267	assert.NoError(t, err)
268	err = os.RemoveAll(user.GetHomeDir())
269	assert.NoError(t, err)
270	conn, client, err := getSftpClient(user)
271	if assert.NoError(t, err) {
272		defer conn.Close()
273		defer client.Close()
274		f, err := client.Create(testFileName)
275		assert.NoError(t, err)
276		_, err = f.Write(testFileContent)
277		assert.NoError(t, err)
278		err = f.Close()
279		assert.NoError(t, err)
280		acmodTime := time.Now().Add(36 * time.Hour)
281		err = client.Chtimes(testFileName, acmodTime, acmodTime)
282		assert.NoError(t, err)
283		newFi, err := client.Lstat(testFileName)
284		assert.NoError(t, err)
285		diff := math.Abs(newFi.ModTime().Sub(acmodTime).Seconds())
286		assert.LessOrEqual(t, diff, float64(1))
287		if runtime.GOOS != osWindows {
288			err = client.Chown(testFileName, os.Getuid(), os.Getgid())
289			assert.NoError(t, err)
290		}
291		newPerm := os.FileMode(0666)
292		err = client.Chmod(testFileName, newPerm)
293		assert.NoError(t, err)
294		newFi, err = client.Lstat(testFileName)
295		if assert.NoError(t, err) {
296			assert.Equal(t, newPerm, newFi.Mode().Perm())
297		}
298		err = client.Truncate(testFileName, 2)
299		assert.NoError(t, err)
300		info, err := client.Stat(testFileName)
301		if assert.NoError(t, err) {
302			assert.Equal(t, int64(2), info.Size())
303		}
304		err = client.Remove(testFileName)
305		assert.NoError(t, err)
306
307		err = client.Truncate(testFileName, 0)
308		assert.ErrorIs(t, err, os.ErrNotExist)
309		err = client.Chtimes(testFileName, acmodTime, acmodTime)
310		assert.ErrorIs(t, err, os.ErrNotExist)
311		if runtime.GOOS != osWindows {
312			err = client.Chown(testFileName, os.Getuid(), os.Getgid())
313			assert.ErrorIs(t, err, os.ErrNotExist)
314		}
315		err = client.Chmod(testFileName, newPerm)
316		assert.ErrorIs(t, err, os.ErrNotExist)
317	}
318	_, err = httpdtest.RemoveUser(user, http.StatusOK)
319	assert.NoError(t, err)
320	err = os.RemoveAll(user.GetHomeDir())
321	assert.NoError(t, err)
322}
323
324func TestChtimesOpenHandle(t *testing.T) {
325	localUser, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
326	assert.NoError(t, err)
327	sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(), http.StatusCreated)
328	assert.NoError(t, err)
329	u := getCryptFsUser()
330	u.Username += "_crypt"
331	cryptFsUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
332	assert.NoError(t, err)
333
334	for _, user := range []dataprovider.User{localUser, sftpUser, cryptFsUser} {
335		conn, client, err := getSftpClient(user)
336		if assert.NoError(t, err) {
337			defer conn.Close()
338			defer client.Close()
339
340			f, err := client.Create(testFileName)
341			assert.NoError(t, err, "user %v", user.Username)
342			f1, err := client.Create(testFileName + "1")
343			assert.NoError(t, err, "user %v", user.Username)
344			acmodTime := time.Now().Add(36 * time.Hour)
345			err = client.Chtimes(testFileName, acmodTime, acmodTime)
346			assert.NoError(t, err, "user %v", user.Username)
347			_, err = f.Write(testFileContent)
348			assert.NoError(t, err, "user %v", user.Username)
349			err = f.Close()
350			assert.NoError(t, err, "user %v", user.Username)
351			err = f1.Close()
352			assert.NoError(t, err, "user %v", user.Username)
353			info, err := client.Lstat(testFileName)
354			assert.NoError(t, err, "user %v", user.Username)
355			diff := math.Abs(info.ModTime().Sub(acmodTime).Seconds())
356			assert.LessOrEqual(t, diff, float64(1), "user %v", user.Username)
357			info1, err := client.Lstat(testFileName + "1")
358			assert.NoError(t, err, "user %v", user.Username)
359			diff = math.Abs(info1.ModTime().Sub(acmodTime).Seconds())
360			assert.Greater(t, diff, float64(86400), "user %v", user.Username)
361		}
362	}
363
364	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
365	assert.NoError(t, err)
366	_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
367	assert.NoError(t, err)
368	err = os.RemoveAll(localUser.GetHomeDir())
369	assert.NoError(t, err)
370	_, err = httpdtest.RemoveUser(cryptFsUser, http.StatusOK)
371	assert.NoError(t, err)
372	err = os.RemoveAll(cryptFsUser.GetHomeDir())
373	assert.NoError(t, err)
374}
375
376func TestPermissionErrors(t *testing.T) {
377	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
378	assert.NoError(t, err)
379	u := getTestSFTPUser()
380	subDir := "/sub"
381	u.Permissions[subDir] = nil
382	sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
383	assert.NoError(t, err)
384	conn, client, err := getSftpClient(user)
385	if assert.NoError(t, err) {
386		defer conn.Close()
387		defer client.Close()
388		err = client.MkdirAll(path.Join(subDir, subDir))
389		assert.NoError(t, err)
390		f, err := client.Create(path.Join(subDir, subDir, testFileName))
391		if assert.NoError(t, err) {
392			_, err = f.Write(testFileContent)
393			assert.NoError(t, err)
394			err = f.Close()
395			assert.NoError(t, err)
396		}
397	}
398	conn, client, err = getSftpClient(sftpUser)
399	if assert.NoError(t, err) {
400		defer conn.Close()
401		defer client.Close()
402		assert.NoError(t, checkBasicSFTP(client))
403		_, err = client.ReadDir(subDir)
404		assert.ErrorIs(t, err, os.ErrPermission)
405		err = client.Mkdir(path.Join(subDir, subDir))
406		assert.ErrorIs(t, err, os.ErrPermission)
407		err = client.RemoveDirectory(path.Join(subDir, subDir))
408		assert.ErrorIs(t, err, os.ErrPermission)
409		err = client.Symlink("test", path.Join(subDir, subDir))
410		assert.ErrorIs(t, err, os.ErrPermission)
411		err = client.Chmod(path.Join(subDir, subDir), os.ModePerm)
412		assert.ErrorIs(t, err, os.ErrPermission)
413		err = client.Chown(path.Join(subDir, subDir), os.Getuid(), os.Getgid())
414		assert.ErrorIs(t, err, os.ErrPermission)
415		err = client.Chtimes(path.Join(subDir, subDir), time.Now(), time.Now())
416		assert.ErrorIs(t, err, os.ErrPermission)
417		err = client.Truncate(path.Join(subDir, subDir), 0)
418		assert.ErrorIs(t, err, os.ErrPermission)
419		err = client.Remove(path.Join(subDir, subDir, testFileName))
420		assert.ErrorIs(t, err, os.ErrPermission)
421	}
422	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
423	assert.NoError(t, err)
424	_, err = httpdtest.RemoveUser(user, http.StatusOK)
425	assert.NoError(t, err)
426	err = os.RemoveAll(user.GetHomeDir())
427	assert.NoError(t, err)
428}
429
430func TestFileNotAllowedErrors(t *testing.T) {
431	deniedDir := "/denied"
432	u := getTestUser()
433	u.Filters.FilePatterns = []sdk.PatternsFilter{
434		{
435			Path:           deniedDir,
436			DeniedPatterns: []string{"*.txt"},
437		},
438	}
439	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
440	assert.NoError(t, err)
441	conn, client, err := getSftpClient(user)
442	if assert.NoError(t, err) {
443		defer conn.Close()
444		defer client.Close()
445		testFile := filepath.Join(u.GetHomeDir(), deniedDir, "file.txt")
446		err = os.MkdirAll(filepath.Join(u.GetHomeDir(), deniedDir), os.ModePerm)
447		assert.NoError(t, err)
448		err = os.WriteFile(testFile, testFileContent, os.ModePerm)
449		assert.NoError(t, err)
450		err = client.Remove(path.Join(deniedDir, "file.txt"))
451		// the sftp client will try to remove the path as directory after receiving
452		// a permission denied error, so we get a generic failure here
453		assert.Error(t, err)
454		err = client.Rename(path.Join(deniedDir, "file.txt"), path.Join(deniedDir, "file1.txt"))
455		assert.ErrorIs(t, err, os.ErrPermission)
456	}
457
458	_, err = httpdtest.RemoveUser(user, http.StatusOK)
459	assert.NoError(t, err)
460	err = os.RemoveAll(user.GetHomeDir())
461	assert.NoError(t, err)
462}
463
464func TestTruncateQuotaLimits(t *testing.T) {
465	u := getTestUser()
466	u.QuotaSize = 20
467	mappedPath1 := filepath.Join(os.TempDir(), "mapped1")
468	folderName1 := filepath.Base(mappedPath1)
469	vdirPath1 := "/vmapped1"
470	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
471		BaseVirtualFolder: vfs.BaseVirtualFolder{
472			Name:       folderName1,
473			MappedPath: mappedPath1,
474		},
475		VirtualPath: vdirPath1,
476		QuotaFiles:  10,
477	})
478	mappedPath2 := filepath.Join(os.TempDir(), "mapped2")
479	folderName2 := filepath.Base(mappedPath2)
480	vdirPath2 := "/vmapped2"
481	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
482		BaseVirtualFolder: vfs.BaseVirtualFolder{
483			Name:       folderName2,
484			MappedPath: mappedPath2,
485		},
486		VirtualPath: vdirPath2,
487		QuotaFiles:  -1,
488		QuotaSize:   -1,
489	})
490	localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
491	assert.NoError(t, err)
492	u = getTestSFTPUser()
493	u.QuotaSize = 20
494	sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
495	assert.NoError(t, err)
496	for _, user := range []dataprovider.User{localUser, sftpUser} {
497		conn, client, err := getSftpClient(user)
498		if assert.NoError(t, err) {
499			defer conn.Close()
500			defer client.Close()
501			f, err := client.OpenFile(testFileName, os.O_WRONLY)
502			if assert.NoError(t, err) {
503				n, err := f.Write(testFileContent)
504				assert.NoError(t, err)
505				assert.Equal(t, len(testFileContent), n)
506				err = f.Truncate(2)
507				assert.NoError(t, err)
508				expectedQuotaFiles := 0
509				expectedQuotaSize := int64(2)
510				user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
511				assert.NoError(t, err)
512				assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
513				assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
514				_, err = f.Seek(expectedQuotaSize, io.SeekStart)
515				assert.NoError(t, err)
516				n, err = f.Write(testFileContent)
517				assert.NoError(t, err)
518				assert.Equal(t, len(testFileContent), n)
519				err = f.Truncate(5)
520				assert.NoError(t, err)
521				expectedQuotaSize = int64(5)
522				user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
523				assert.NoError(t, err)
524				assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
525				assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
526				_, err = f.Seek(expectedQuotaSize, io.SeekStart)
527				assert.NoError(t, err)
528				n, err = f.Write(testFileContent)
529				assert.NoError(t, err)
530				assert.Equal(t, len(testFileContent), n)
531				err = f.Close()
532				assert.NoError(t, err)
533				expectedQuotaFiles = 1
534				expectedQuotaSize = int64(5) + int64(len(testFileContent))
535				user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
536				assert.NoError(t, err)
537				assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
538				assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
539			}
540			// now truncate by path
541			err = client.Truncate(testFileName, 5)
542			assert.NoError(t, err)
543			user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
544			assert.NoError(t, err)
545			assert.Equal(t, 1, user.UsedQuotaFiles)
546			assert.Equal(t, int64(5), user.UsedQuotaSize)
547			// now open an existing file without truncate it, quota should not change
548			f, err = client.OpenFile(testFileName, os.O_WRONLY)
549			if assert.NoError(t, err) {
550				err = f.Close()
551				assert.NoError(t, err)
552				user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
553				assert.NoError(t, err)
554				assert.Equal(t, 1, user.UsedQuotaFiles)
555				assert.Equal(t, int64(5), user.UsedQuotaSize)
556			}
557			// open the file truncating it
558			f, err = client.OpenFile(testFileName, os.O_WRONLY|os.O_TRUNC)
559			if assert.NoError(t, err) {
560				err = f.Close()
561				assert.NoError(t, err)
562				user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
563				assert.NoError(t, err)
564				assert.Equal(t, 1, user.UsedQuotaFiles)
565				assert.Equal(t, int64(0), user.UsedQuotaSize)
566			}
567			// now test max write size
568			f, err = client.OpenFile(testFileName, os.O_WRONLY)
569			if assert.NoError(t, err) {
570				n, err := f.Write(testFileContent)
571				assert.NoError(t, err)
572				assert.Equal(t, len(testFileContent), n)
573				err = f.Truncate(11)
574				assert.NoError(t, err)
575				user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
576				assert.NoError(t, err)
577				assert.Equal(t, 1, user.UsedQuotaFiles)
578				assert.Equal(t, int64(11), user.UsedQuotaSize)
579				_, err = f.Seek(int64(11), io.SeekStart)
580				assert.NoError(t, err)
581				n, err = f.Write(testFileContent)
582				assert.NoError(t, err)
583				assert.Equal(t, len(testFileContent), n)
584				err = f.Truncate(5)
585				assert.NoError(t, err)
586				user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
587				assert.NoError(t, err)
588				assert.Equal(t, 1, user.UsedQuotaFiles)
589				assert.Equal(t, int64(5), user.UsedQuotaSize)
590				_, err = f.Seek(int64(5), io.SeekStart)
591				assert.NoError(t, err)
592				n, err = f.Write(testFileContent)
593				assert.NoError(t, err)
594				assert.Equal(t, len(testFileContent), n)
595				err = f.Truncate(12)
596				assert.NoError(t, err)
597				user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
598				assert.NoError(t, err)
599				assert.Equal(t, 1, user.UsedQuotaFiles)
600				assert.Equal(t, int64(12), user.UsedQuotaSize)
601				_, err = f.Seek(int64(12), io.SeekStart)
602				assert.NoError(t, err)
603				_, err = f.Write(testFileContent)
604				if assert.Error(t, err) {
605					assert.Contains(t, err.Error(), common.ErrQuotaExceeded.Error())
606				}
607				err = f.Close()
608				assert.Error(t, err)
609				// the file is deleted
610				user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
611				assert.NoError(t, err)
612				assert.Equal(t, 0, user.UsedQuotaFiles)
613				assert.Equal(t, int64(0), user.UsedQuotaSize)
614			}
615
616			if user.Username == defaultUsername {
617				// basic test inside a virtual folder
618				vfileName1 := path.Join(vdirPath1, testFileName)
619				f, err = client.OpenFile(vfileName1, os.O_WRONLY)
620				if assert.NoError(t, err) {
621					n, err := f.Write(testFileContent)
622					assert.NoError(t, err)
623					assert.Equal(t, len(testFileContent), n)
624					err = f.Truncate(2)
625					assert.NoError(t, err)
626					expectedQuotaFiles := 0
627					expectedQuotaSize := int64(2)
628					fold, _, err := httpdtest.GetFolderByName(folderName1, http.StatusOK)
629					assert.NoError(t, err)
630					assert.Equal(t, expectedQuotaSize, fold.UsedQuotaSize)
631					assert.Equal(t, expectedQuotaFiles, fold.UsedQuotaFiles)
632					err = f.Close()
633					assert.NoError(t, err)
634					expectedQuotaFiles = 1
635					fold, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
636					assert.NoError(t, err)
637					assert.Equal(t, expectedQuotaSize, fold.UsedQuotaSize)
638					assert.Equal(t, expectedQuotaFiles, fold.UsedQuotaFiles)
639				}
640				err = client.Truncate(vfileName1, 1)
641				assert.NoError(t, err)
642				fold, _, err := httpdtest.GetFolderByName(folderName1, http.StatusOK)
643				assert.NoError(t, err)
644				assert.Equal(t, int64(1), fold.UsedQuotaSize)
645				assert.Equal(t, 1, fold.UsedQuotaFiles)
646				// now test on vdirPath2, the folder quota is included in the user's quota
647				vfileName2 := path.Join(vdirPath2, testFileName)
648				f, err = client.OpenFile(vfileName2, os.O_WRONLY)
649				if assert.NoError(t, err) {
650					n, err := f.Write(testFileContent)
651					assert.NoError(t, err)
652					assert.Equal(t, len(testFileContent), n)
653					err = f.Truncate(3)
654					assert.NoError(t, err)
655					expectedQuotaFiles := 0
656					expectedQuotaSize := int64(3)
657					fold, _, err := httpdtest.GetFolderByName(folderName2, http.StatusOK)
658					assert.NoError(t, err)
659					assert.Equal(t, expectedQuotaSize, fold.UsedQuotaSize)
660					assert.Equal(t, expectedQuotaFiles, fold.UsedQuotaFiles)
661					err = f.Close()
662					assert.NoError(t, err)
663					expectedQuotaFiles = 1
664					fold, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
665					assert.NoError(t, err)
666					assert.Equal(t, expectedQuotaSize, fold.UsedQuotaSize)
667					assert.Equal(t, expectedQuotaFiles, fold.UsedQuotaFiles)
668					user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
669					assert.NoError(t, err)
670					assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
671					assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
672				}
673
674				// cleanup
675				err = os.RemoveAll(user.GetHomeDir())
676				assert.NoError(t, err)
677				user.UsedQuotaFiles = 0
678				user.UsedQuotaSize = 0
679				_, err = httpdtest.UpdateQuotaUsage(user, "reset", http.StatusOK)
680				assert.NoError(t, err)
681				user.QuotaSize = 0
682				_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
683				assert.NoError(t, err)
684			}
685		}
686	}
687	_, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
688	assert.NoError(t, err)
689	_, err = httpdtest.RemoveUser(localUser, http.StatusOK)
690	assert.NoError(t, err)
691	err = os.RemoveAll(localUser.GetHomeDir())
692	assert.NoError(t, err)
693	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
694	assert.NoError(t, err)
695	err = os.RemoveAll(mappedPath1)
696	assert.NoError(t, err)
697	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
698	assert.NoError(t, err)
699	err = os.RemoveAll(mappedPath2)
700	assert.NoError(t, err)
701}
702
703func TestVirtualFoldersQuotaRenameOverwrite(t *testing.T) {
704	testFileSize := int64(131072)
705	testFileSize1 := int64(65537)
706	testFileName1 := "test_file1.dat" //nolint:goconst
707	u := getTestUser()
708	u.QuotaFiles = 0
709	u.QuotaSize = 0
710	mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
711	folderName1 := filepath.Base(mappedPath1)
712	vdirPath1 := "/vdir1" //nolint:goconst
713	mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
714	folderName2 := filepath.Base(mappedPath2)
715	vdirPath2 := "/vdir2" //nolint:goconst
716	mappedPath3 := filepath.Join(os.TempDir(), "vdir3")
717	folderName3 := filepath.Base(mappedPath3)
718	vdirPath3 := "/vdir3"
719	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
720		BaseVirtualFolder: vfs.BaseVirtualFolder{
721			Name:       folderName1,
722			MappedPath: mappedPath1,
723		},
724		VirtualPath: vdirPath1,
725		QuotaFiles:  2,
726		QuotaSize:   0,
727	})
728	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
729		BaseVirtualFolder: vfs.BaseVirtualFolder{
730			MappedPath: mappedPath2,
731			Name:       folderName2,
732		},
733		VirtualPath: vdirPath2,
734		QuotaFiles:  0,
735		QuotaSize:   testFileSize + testFileSize1 + 1,
736	})
737	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
738		BaseVirtualFolder: vfs.BaseVirtualFolder{
739			Name:       folderName3,
740			MappedPath: mappedPath3,
741		},
742		VirtualPath: vdirPath3,
743		QuotaFiles:  2,
744		QuotaSize:   testFileSize * 2,
745	})
746	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
747	assert.NoError(t, err)
748	conn, client, err := getSftpClient(user)
749	if assert.NoError(t, err) {
750		defer conn.Close()
751		defer client.Close()
752		err = writeSFTPFile(path.Join(vdirPath1, testFileName), testFileSize, client)
753		assert.NoError(t, err)
754		err = writeSFTPFile(path.Join(vdirPath2, testFileName), testFileSize, client)
755		assert.NoError(t, err)
756		err = writeSFTPFile(path.Join(vdirPath1, testFileName1), testFileSize1, client)
757		assert.NoError(t, err)
758		err = writeSFTPFile(path.Join(vdirPath2, testFileName1), testFileSize1, client)
759		assert.NoError(t, err)
760		err = writeSFTPFile(testFileName, testFileSize, client)
761		assert.NoError(t, err)
762		err = writeSFTPFile(testFileName1, testFileSize1, client)
763		assert.NoError(t, err)
764		err = writeSFTPFile(path.Join(vdirPath3, testFileName), testFileSize, client)
765		assert.NoError(t, err)
766		err = writeSFTPFile(path.Join(vdirPath3, testFileName+"1"), testFileSize, client)
767		assert.NoError(t, err)
768		err = client.Rename(testFileName, path.Join(vdirPath1, testFileName+".rename"))
769		assert.Error(t, err)
770		// we overwrite an existing file and we have unlimited size
771		err = client.Rename(testFileName, path.Join(vdirPath1, testFileName))
772		assert.NoError(t, err)
773		// we have no space and we try to overwrite a bigger file with a smaller one, this should succeed
774		err = client.Rename(testFileName1, path.Join(vdirPath2, testFileName))
775		assert.NoError(t, err)
776		err = writeSFTPFile(path.Join(vdirPath2, testFileName), testFileSize, client)
777		assert.NoError(t, err)
778		err = writeSFTPFile(testFileName, testFileSize, client)
779		assert.NoError(t, err)
780		// we have no space and we try to overwrite a smaller file with a bigger one, this should fail
781		err = client.Rename(testFileName, path.Join(vdirPath2, testFileName1))
782		assert.Error(t, err)
783		fi, err := client.Stat(path.Join(vdirPath1, testFileName1))
784		if assert.NoError(t, err) {
785			assert.Equal(t, testFileSize1, fi.Size())
786		}
787		// we are overquota inside vdir3 size 2/2 and size 262144/262144
788		err = client.Rename(path.Join(vdirPath1, testFileName1), path.Join(vdirPath3, testFileName1+".rename"))
789		assert.Error(t, err)
790		// we overwrite an existing file and we have enough size
791		err = client.Rename(path.Join(vdirPath1, testFileName1), path.Join(vdirPath3, testFileName))
792		assert.NoError(t, err)
793		testFileName2 := "test_file2.dat"
794		err = writeSFTPFile(testFileName2, testFileSize+testFileSize1, client)
795		assert.NoError(t, err)
796		// we overwrite an existing file and we haven't enough size
797		err = client.Rename(testFileName2, path.Join(vdirPath3, testFileName))
798		assert.Error(t, err)
799		// now remove a file from vdir3, create a dir with 2 files and try to rename it in vdir3
800		// this will fail since the rename will result in 3 files inside vdir3 and quota limits only
801		// allow 2 total files there
802		err = client.Remove(path.Join(vdirPath3, testFileName+"1"))
803		assert.NoError(t, err)
804		aDir := "a dir"
805		err = client.Mkdir(aDir)
806		assert.NoError(t, err)
807		err = writeSFTPFile(path.Join(aDir, testFileName1), testFileSize1, client)
808		assert.NoError(t, err)
809		err = writeSFTPFile(path.Join(aDir, testFileName1+"1"), testFileSize1, client)
810		assert.NoError(t, err)
811		err = client.Rename(aDir, path.Join(vdirPath3, aDir))
812		assert.Error(t, err)
813	}
814	_, err = httpdtest.RemoveUser(user, http.StatusOK)
815	assert.NoError(t, err)
816	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
817	assert.NoError(t, err)
818	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
819	assert.NoError(t, err)
820	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName3}, http.StatusOK)
821	assert.NoError(t, err)
822	err = os.RemoveAll(user.GetHomeDir())
823	assert.NoError(t, err)
824	err = os.RemoveAll(mappedPath1)
825	assert.NoError(t, err)
826	err = os.RemoveAll(mappedPath2)
827	assert.NoError(t, err)
828	err = os.RemoveAll(mappedPath3)
829	assert.NoError(t, err)
830}
831
832func TestQuotaRenameOverwrite(t *testing.T) {
833	u := getTestUser()
834	u.QuotaFiles = 100
835	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
836	assert.NoError(t, err)
837	conn, client, err := getSftpClient(user)
838	if assert.NoError(t, err) {
839		defer conn.Close()
840		defer client.Close()
841		testFileSize := int64(131072)
842		testFileSize1 := int64(65537)
843		testFileName1 := "test_file1.dat"
844		err = writeSFTPFile(testFileName, testFileSize, client)
845		assert.NoError(t, err)
846		err = writeSFTPFile(testFileName1, testFileSize1, client)
847		assert.NoError(t, err)
848		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
849		assert.NoError(t, err)
850		assert.Equal(t, 2, user.UsedQuotaFiles)
851		assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
852		err = client.Rename(testFileName, testFileName1)
853		assert.NoError(t, err)
854		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
855		assert.NoError(t, err)
856		assert.Equal(t, 1, user.UsedQuotaFiles)
857		assert.Equal(t, testFileSize, user.UsedQuotaSize)
858		err = client.Remove(testFileName1)
859		assert.NoError(t, err)
860		err = writeSFTPFile(testFileName, testFileSize, client)
861		assert.NoError(t, err)
862		err = writeSFTPFile(testFileName1, testFileSize1, client)
863		assert.NoError(t, err)
864		err = client.Rename(testFileName1, testFileName)
865		assert.NoError(t, err)
866		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
867		assert.NoError(t, err)
868		assert.Equal(t, 1, user.UsedQuotaFiles)
869		assert.Equal(t, testFileSize1, user.UsedQuotaSize)
870	}
871	_, err = httpdtest.RemoveUser(user, http.StatusOK)
872	assert.NoError(t, err)
873	err = os.RemoveAll(user.GetHomeDir())
874	assert.NoError(t, err)
875}
876
877func TestVirtualFoldersQuotaValues(t *testing.T) {
878	u := getTestUser()
879	u.QuotaFiles = 100
880	mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
881	vdirPath1 := "/vdir1"
882	folderName1 := filepath.Base(mappedPath1)
883	mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
884	vdirPath2 := "/vdir2"
885	folderName2 := filepath.Base(mappedPath2)
886	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
887		BaseVirtualFolder: vfs.BaseVirtualFolder{
888			Name:       folderName1,
889			MappedPath: mappedPath1,
890		},
891		VirtualPath: vdirPath1,
892		// quota is included in the user's one
893		QuotaFiles: -1,
894		QuotaSize:  -1,
895	})
896	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
897		BaseVirtualFolder: vfs.BaseVirtualFolder{
898			Name:       folderName2,
899			MappedPath: mappedPath2,
900		},
901		VirtualPath: vdirPath2,
902		// quota is unlimited and excluded from user's one
903		QuotaFiles: 0,
904		QuotaSize:  0,
905	})
906	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
907	assert.NoError(t, err)
908	conn, client, err := getSftpClient(user)
909	if assert.NoError(t, err) {
910		defer conn.Close()
911		defer client.Close()
912		testFileSize := int64(131072)
913		err = writeSFTPFile(testFileName, testFileSize, client)
914		assert.NoError(t, err)
915		// we copy the same file two times to test quota update on file overwrite
916		err = writeSFTPFile(path.Join(vdirPath1, testFileName), testFileSize, client)
917		assert.NoError(t, err)
918		err = writeSFTPFile(path.Join(vdirPath1, testFileName), testFileSize, client)
919		assert.NoError(t, err)
920		err = writeSFTPFile(path.Join(vdirPath2, testFileName), testFileSize, client)
921		assert.NoError(t, err)
922		err = writeSFTPFile(path.Join(vdirPath2, testFileName), testFileSize, client)
923		assert.NoError(t, err)
924		expectedQuotaFiles := 2
925		expectedQuotaSize := testFileSize * 2
926		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
927		assert.NoError(t, err)
928		assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
929		assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
930		f, _, err := httpdtest.GetFolderByName(folderName1, http.StatusOK)
931		assert.NoError(t, err)
932		assert.Equal(t, testFileSize, f.UsedQuotaSize)
933		assert.Equal(t, 1, f.UsedQuotaFiles)
934		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
935		assert.NoError(t, err)
936		assert.Equal(t, testFileSize, f.UsedQuotaSize)
937		assert.Equal(t, 1, f.UsedQuotaFiles)
938
939		err = client.Remove(path.Join(vdirPath1, testFileName))
940		assert.NoError(t, err)
941		err = client.Remove(path.Join(vdirPath2, testFileName))
942		assert.NoError(t, err)
943
944		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
945		assert.NoError(t, err)
946		assert.Equal(t, int64(0), f.UsedQuotaSize)
947		assert.Equal(t, 0, f.UsedQuotaFiles)
948		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
949		assert.NoError(t, err)
950		assert.Equal(t, int64(0), f.UsedQuotaSize)
951		assert.Equal(t, 0, f.UsedQuotaFiles)
952	}
953	_, err = httpdtest.RemoveUser(user, http.StatusOK)
954	assert.NoError(t, err)
955	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
956	assert.NoError(t, err)
957	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
958	assert.NoError(t, err)
959	err = os.RemoveAll(user.GetHomeDir())
960	assert.NoError(t, err)
961	err = os.RemoveAll(mappedPath1)
962	assert.NoError(t, err)
963	err = os.RemoveAll(mappedPath2)
964	assert.NoError(t, err)
965}
966
967func TestQuotaRenameInsideSameVirtualFolder(t *testing.T) {
968	u := getTestUser()
969	u.QuotaFiles = 100
970	mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
971	vdirPath1 := "/vdir1"
972	folderName1 := filepath.Base(mappedPath1)
973	mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
974	vdirPath2 := "/vdir2"
975	folderName2 := filepath.Base(mappedPath2)
976	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
977		BaseVirtualFolder: vfs.BaseVirtualFolder{
978			Name:       folderName1,
979			MappedPath: mappedPath1,
980		},
981		VirtualPath: vdirPath1,
982		// quota is included in the user's one
983		QuotaFiles: -1,
984		QuotaSize:  -1,
985	})
986	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
987		BaseVirtualFolder: vfs.BaseVirtualFolder{
988			Name:       folderName2,
989			MappedPath: mappedPath2,
990		},
991		VirtualPath: vdirPath2,
992		// quota is unlimited and excluded from user's one
993		QuotaFiles: 0,
994		QuotaSize:  0,
995	})
996	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
997	assert.NoError(t, err)
998	conn, client, err := getSftpClient(user)
999	if assert.NoError(t, err) {
1000		defer conn.Close()
1001		defer client.Close()
1002		testFileName1 := "test_file1.dat"
1003		testFileSize := int64(131072)
1004		testFileSize1 := int64(65535)
1005		dir1 := "dir1" //nolint:goconst
1006		dir2 := "dir2" //nolint:goconst
1007		assert.NoError(t, err)
1008		err = client.Mkdir(path.Join(vdirPath1, dir1))
1009		assert.NoError(t, err)
1010		err = client.Mkdir(path.Join(vdirPath1, dir2))
1011		assert.NoError(t, err)
1012		err = client.Mkdir(path.Join(vdirPath2, dir1))
1013		assert.NoError(t, err)
1014		err = client.Mkdir(path.Join(vdirPath2, dir2))
1015		assert.NoError(t, err)
1016		err = writeSFTPFile(path.Join(vdirPath1, dir1, testFileName), testFileSize, client)
1017		assert.NoError(t, err)
1018		err = writeSFTPFile(path.Join(vdirPath1, dir2, testFileName1), testFileSize1, client)
1019		assert.NoError(t, err)
1020		err = writeSFTPFile(path.Join(vdirPath2, dir1, testFileName), testFileSize, client)
1021		assert.NoError(t, err)
1022		err = writeSFTPFile(path.Join(vdirPath2, dir2, testFileName1), testFileSize1, client)
1023		assert.NoError(t, err)
1024		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1025		assert.NoError(t, err)
1026		assert.Equal(t, 2, user.UsedQuotaFiles)
1027		assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
1028		f, _, err := httpdtest.GetFolderByName(folderName1, http.StatusOK)
1029		assert.NoError(t, err)
1030		assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize)
1031		assert.Equal(t, 2, f.UsedQuotaFiles)
1032		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
1033		assert.NoError(t, err)
1034		assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize)
1035		assert.Equal(t, 2, f.UsedQuotaFiles)
1036		// initial files:
1037		// - vdir1/dir1/testFileName
1038		// - vdir1/dir2/testFileName1
1039		// - vdir2/dir1/testFileName
1040		// - vdir2/dir2/testFileName1
1041		//
1042		// rename a file inside vdir1 it is included inside user quota, so we have:
1043		// - vdir1/dir1/testFileName.rename
1044		// - vdir1/dir2/testFileName1
1045		// - vdir2/dir1/testFileName
1046		// - vdir2/dir2/testFileName1
1047		err = client.Rename(path.Join(vdirPath1, dir1, testFileName), path.Join(vdirPath1, dir1, testFileName+".rename"))
1048		assert.NoError(t, err)
1049		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1050		assert.NoError(t, err)
1051		assert.Equal(t, 2, user.UsedQuotaFiles)
1052		assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
1053		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
1054		assert.NoError(t, err)
1055		assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize)
1056		assert.Equal(t, 2, f.UsedQuotaFiles)
1057		// rename a file inside vdir2, it isn't included inside user quota, so we have:
1058		// - vdir1/dir1/testFileName.rename
1059		// - vdir1/dir2/testFileName1
1060		// - vdir2/dir1/testFileName.rename
1061		// - vdir2/dir2/testFileName1
1062		err = client.Rename(path.Join(vdirPath2, dir1, testFileName), path.Join(vdirPath2, dir1, testFileName+".rename"))
1063		assert.NoError(t, err)
1064		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1065		assert.NoError(t, err)
1066		assert.Equal(t, 2, user.UsedQuotaFiles)
1067		assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
1068		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
1069		assert.NoError(t, err)
1070		assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize)
1071		assert.Equal(t, 2, f.UsedQuotaFiles)
1072		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
1073		assert.NoError(t, err)
1074		assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize)
1075		assert.Equal(t, 2, f.UsedQuotaFiles)
1076		// rename a file inside vdir2 overwriting an existing, we now have:
1077		// - vdir1/dir1/testFileName.rename
1078		// - vdir1/dir2/testFileName1
1079		// - vdir2/dir1/testFileName.rename (initial testFileName1)
1080		err = client.Rename(path.Join(vdirPath2, dir2, testFileName1), path.Join(vdirPath2, dir1, testFileName+".rename"))
1081		assert.NoError(t, err)
1082		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1083		assert.NoError(t, err)
1084		assert.Equal(t, 2, user.UsedQuotaFiles)
1085		assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
1086		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
1087		assert.NoError(t, err)
1088		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
1089		assert.Equal(t, 1, f.UsedQuotaFiles)
1090		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
1091		assert.NoError(t, err)
1092		assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize)
1093		assert.Equal(t, 2, f.UsedQuotaFiles)
1094		// rename a file inside vdir1 overwriting an existing, we now have:
1095		// - vdir1/dir1/testFileName.rename (initial testFileName1)
1096		// - vdir2/dir1/testFileName.rename (initial testFileName1)
1097		err = client.Rename(path.Join(vdirPath1, dir2, testFileName1), path.Join(vdirPath1, dir1, testFileName+".rename"))
1098		assert.NoError(t, err)
1099		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1100		assert.NoError(t, err)
1101		assert.Equal(t, 1, user.UsedQuotaFiles)
1102		assert.Equal(t, testFileSize1, user.UsedQuotaSize)
1103		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
1104		assert.NoError(t, err)
1105		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
1106		assert.Equal(t, 1, f.UsedQuotaFiles)
1107		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
1108		assert.NoError(t, err)
1109		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
1110		assert.Equal(t, 1, f.UsedQuotaFiles)
1111		// rename a directory inside the same virtual folder, quota should not change
1112		err = client.RemoveDirectory(path.Join(vdirPath1, dir2))
1113		assert.NoError(t, err)
1114		err = client.RemoveDirectory(path.Join(vdirPath2, dir2))
1115		assert.NoError(t, err)
1116		err = client.Rename(path.Join(vdirPath1, dir1), path.Join(vdirPath1, dir2))
1117		assert.NoError(t, err)
1118		err = client.Rename(path.Join(vdirPath2, dir1), path.Join(vdirPath2, dir2))
1119		assert.NoError(t, err)
1120		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1121		assert.NoError(t, err)
1122		assert.Equal(t, 1, user.UsedQuotaFiles)
1123		assert.Equal(t, testFileSize1, user.UsedQuotaSize)
1124		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
1125		assert.NoError(t, err)
1126		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
1127		assert.Equal(t, 1, f.UsedQuotaFiles)
1128		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
1129		assert.NoError(t, err)
1130		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
1131		assert.Equal(t, 1, f.UsedQuotaFiles)
1132	}
1133	_, err = httpdtest.RemoveUser(user, http.StatusOK)
1134	assert.NoError(t, err)
1135	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
1136	assert.NoError(t, err)
1137	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
1138	assert.NoError(t, err)
1139	err = os.RemoveAll(user.GetHomeDir())
1140	assert.NoError(t, err)
1141	err = os.RemoveAll(mappedPath1)
1142	assert.NoError(t, err)
1143	err = os.RemoveAll(mappedPath2)
1144	assert.NoError(t, err)
1145}
1146
1147func TestQuotaRenameBetweenVirtualFolder(t *testing.T) {
1148	u := getTestUser()
1149	u.QuotaFiles = 100
1150	mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
1151	folderName1 := filepath.Base(mappedPath1)
1152	vdirPath1 := "/vdir1"
1153	mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
1154	folderName2 := filepath.Base(mappedPath2)
1155	vdirPath2 := "/vdir2"
1156	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
1157		BaseVirtualFolder: vfs.BaseVirtualFolder{
1158			Name:       folderName1,
1159			MappedPath: mappedPath1,
1160		},
1161		VirtualPath: vdirPath1,
1162		// quota is included in the user's one
1163		QuotaFiles: -1,
1164		QuotaSize:  -1,
1165	})
1166	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
1167		BaseVirtualFolder: vfs.BaseVirtualFolder{
1168			Name:       folderName2,
1169			MappedPath: mappedPath2,
1170		},
1171		VirtualPath: vdirPath2,
1172		// quota is unlimited and excluded from user's one
1173		QuotaFiles: 0,
1174		QuotaSize:  0,
1175	})
1176	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
1177	assert.NoError(t, err)
1178	conn, client, err := getSftpClient(user)
1179	if assert.NoError(t, err) {
1180		defer conn.Close()
1181		defer client.Close()
1182		testFileName1 := "test_file1.dat"
1183		testFileSize := int64(131072)
1184		testFileSize1 := int64(65535)
1185		dir1 := "dir1"
1186		dir2 := "dir2"
1187		err = client.Mkdir(path.Join(vdirPath1, dir1))
1188		assert.NoError(t, err)
1189		err = client.Mkdir(path.Join(vdirPath1, dir2))
1190		assert.NoError(t, err)
1191		err = client.Mkdir(path.Join(vdirPath2, dir1))
1192		assert.NoError(t, err)
1193		err = client.Mkdir(path.Join(vdirPath2, dir2))
1194		assert.NoError(t, err)
1195		err = writeSFTPFile(path.Join(vdirPath1, dir1, testFileName), testFileSize, client)
1196		assert.NoError(t, err)
1197		err = writeSFTPFile(path.Join(vdirPath1, dir2, testFileName1), testFileSize1, client)
1198		assert.NoError(t, err)
1199		err = writeSFTPFile(path.Join(vdirPath2, dir1, testFileName), testFileSize, client)
1200		assert.NoError(t, err)
1201		err = writeSFTPFile(path.Join(vdirPath2, dir2, testFileName1), testFileSize1, client)
1202		assert.NoError(t, err)
1203		// initial files:
1204		// - vdir1/dir1/testFileName
1205		// - vdir1/dir2/testFileName1
1206		// - vdir2/dir1/testFileName
1207		// - vdir2/dir2/testFileName1
1208		//
1209		// rename a file from vdir1 to vdir2, vdir1 is included inside user quota, so we have:
1210		// - vdir1/dir1/testFileName
1211		// - vdir2/dir1/testFileName
1212		// - vdir2/dir2/testFileName1
1213		// - vdir2/dir1/testFileName1.rename
1214		err = client.Rename(path.Join(vdirPath1, dir2, testFileName1), path.Join(vdirPath2, dir1, testFileName1+".rename"))
1215		assert.NoError(t, err)
1216		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1217		assert.NoError(t, err)
1218		assert.Equal(t, 1, user.UsedQuotaFiles)
1219		assert.Equal(t, testFileSize, user.UsedQuotaSize)
1220		f, _, err := httpdtest.GetFolderByName(folderName1, http.StatusOK)
1221		assert.NoError(t, err)
1222		assert.Equal(t, testFileSize, f.UsedQuotaSize)
1223		assert.Equal(t, 1, f.UsedQuotaFiles)
1224		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
1225		assert.NoError(t, err)
1226		assert.Equal(t, testFileSize+testFileSize1+testFileSize1, f.UsedQuotaSize)
1227		assert.Equal(t, 3, f.UsedQuotaFiles)
1228		// rename a file from vdir2 to vdir1, vdir2 is not included inside user quota, so we have:
1229		// - vdir1/dir1/testFileName
1230		// - vdir1/dir2/testFileName.rename
1231		// - vdir2/dir2/testFileName1
1232		// - vdir2/dir1/testFileName1.rename
1233		err = client.Rename(path.Join(vdirPath2, dir1, testFileName), path.Join(vdirPath1, dir2, testFileName+".rename"))
1234		assert.NoError(t, err)
1235		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1236		assert.NoError(t, err)
1237		assert.Equal(t, 2, user.UsedQuotaFiles)
1238		assert.Equal(t, testFileSize*2, user.UsedQuotaSize)
1239		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
1240		assert.NoError(t, err)
1241		assert.Equal(t, testFileSize*2, f.UsedQuotaSize)
1242		assert.Equal(t, 2, f.UsedQuotaFiles)
1243		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
1244		assert.NoError(t, err)
1245		assert.Equal(t, testFileSize1*2, f.UsedQuotaSize)
1246		assert.Equal(t, 2, f.UsedQuotaFiles)
1247		// rename a file from vdir1 to vdir2 overwriting an existing file, vdir1 is included inside user quota, so we have:
1248		// - vdir1/dir2/testFileName.rename
1249		// - vdir2/dir2/testFileName1 (is the initial testFileName)
1250		// - vdir2/dir1/testFileName1.rename
1251		err = client.Rename(path.Join(vdirPath1, dir1, testFileName), path.Join(vdirPath2, dir2, testFileName1))
1252		assert.NoError(t, err)
1253		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1254		assert.NoError(t, err)
1255		assert.Equal(t, 1, user.UsedQuotaFiles)
1256		assert.Equal(t, testFileSize, user.UsedQuotaSize)
1257		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
1258		assert.NoError(t, err)
1259		assert.Equal(t, testFileSize, f.UsedQuotaSize)
1260		assert.Equal(t, 1, f.UsedQuotaFiles)
1261		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
1262		assert.NoError(t, err)
1263		assert.Equal(t, testFileSize1+testFileSize, f.UsedQuotaSize)
1264		assert.Equal(t, 2, f.UsedQuotaFiles)
1265		// rename a file from vdir2 to vdir1 overwriting an existing file, vdir2 is not included inside user quota, so we have:
1266		// - vdir1/dir2/testFileName.rename (is the initial testFileName1)
1267		// - vdir2/dir2/testFileName1 (is the initial testFileName)
1268		err = client.Rename(path.Join(vdirPath2, dir1, testFileName1+".rename"), path.Join(vdirPath1, dir2, testFileName+".rename"))
1269		assert.NoError(t, err)
1270		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1271		assert.NoError(t, err)
1272		assert.Equal(t, 1, user.UsedQuotaFiles)
1273		assert.Equal(t, testFileSize1, user.UsedQuotaSize)
1274		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
1275		assert.NoError(t, err)
1276		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
1277		assert.Equal(t, 1, f.UsedQuotaFiles)
1278		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
1279		assert.NoError(t, err)
1280		assert.Equal(t, testFileSize, f.UsedQuotaSize)
1281		assert.Equal(t, 1, f.UsedQuotaFiles)
1282
1283		err = writeSFTPFile(path.Join(vdirPath1, dir2, testFileName), testFileSize, client)
1284		assert.NoError(t, err)
1285		err = writeSFTPFile(path.Join(vdirPath2, dir2, testFileName), testFileSize1, client)
1286		assert.NoError(t, err)
1287		err = writeSFTPFile(path.Join(vdirPath2, dir2, testFileName+"1.dupl"), testFileSize1, client)
1288		assert.NoError(t, err)
1289		err = client.RemoveDirectory(path.Join(vdirPath1, dir1))
1290		assert.NoError(t, err)
1291		err = client.RemoveDirectory(path.Join(vdirPath2, dir1))
1292		assert.NoError(t, err)
1293		// - vdir1/dir2/testFileName.rename (initial testFileName1)
1294		// - vdir1/dir2/testFileName
1295		// - vdir2/dir2/testFileName1 (initial testFileName)
1296		// - vdir2/dir2/testFileName (initial testFileName1)
1297		// - vdir2/dir2/testFileName1.dupl
1298		// rename directories between the two virtual folders
1299		err = client.Rename(path.Join(vdirPath2, dir2), path.Join(vdirPath1, dir1))
1300		assert.NoError(t, err)
1301		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1302		assert.NoError(t, err)
1303		assert.Equal(t, 5, user.UsedQuotaFiles)
1304		assert.Equal(t, testFileSize1*3+testFileSize*2, user.UsedQuotaSize)
1305		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
1306		assert.NoError(t, err)
1307		assert.Equal(t, testFileSize1*3+testFileSize*2, f.UsedQuotaSize)
1308		assert.Equal(t, 5, f.UsedQuotaFiles)
1309		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
1310		assert.NoError(t, err)
1311		assert.Equal(t, int64(0), f.UsedQuotaSize)
1312		assert.Equal(t, 0, f.UsedQuotaFiles)
1313		// now move on vpath2
1314		err = client.Rename(path.Join(vdirPath1, dir2), path.Join(vdirPath2, dir1))
1315		assert.NoError(t, err)
1316		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1317		assert.NoError(t, err)
1318		assert.Equal(t, 3, user.UsedQuotaFiles)
1319		assert.Equal(t, testFileSize1*2+testFileSize, user.UsedQuotaSize)
1320		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
1321		assert.NoError(t, err)
1322		assert.Equal(t, testFileSize1*2+testFileSize, f.UsedQuotaSize)
1323		assert.Equal(t, 3, f.UsedQuotaFiles)
1324		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
1325		assert.NoError(t, err)
1326		assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize)
1327		assert.Equal(t, 2, f.UsedQuotaFiles)
1328	}
1329	_, err = httpdtest.RemoveUser(user, http.StatusOK)
1330	assert.NoError(t, err)
1331	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
1332	assert.NoError(t, err)
1333	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
1334	assert.NoError(t, err)
1335	err = os.RemoveAll(user.GetHomeDir())
1336	assert.NoError(t, err)
1337	err = os.RemoveAll(mappedPath1)
1338	assert.NoError(t, err)
1339	err = os.RemoveAll(mappedPath2)
1340	assert.NoError(t, err)
1341}
1342
1343func TestQuotaRenameFromVirtualFolder(t *testing.T) {
1344	u := getTestUser()
1345	u.QuotaFiles = 100
1346	mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
1347	folderName1 := filepath.Base(mappedPath1)
1348	vdirPath1 := "/vdir1"
1349	mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
1350	folderName2 := filepath.Base(mappedPath2)
1351	vdirPath2 := "/vdir2"
1352	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
1353		BaseVirtualFolder: vfs.BaseVirtualFolder{
1354			Name:       folderName1,
1355			MappedPath: mappedPath1,
1356		},
1357		VirtualPath: vdirPath1,
1358		// quota is included in the user's one
1359		QuotaFiles: -1,
1360		QuotaSize:  -1,
1361	})
1362	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
1363		BaseVirtualFolder: vfs.BaseVirtualFolder{
1364			Name:       folderName2,
1365			MappedPath: mappedPath2,
1366		},
1367		VirtualPath: vdirPath2,
1368		// quota is unlimited and excluded from user's one
1369		QuotaFiles: 0,
1370		QuotaSize:  0,
1371	})
1372	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
1373	assert.NoError(t, err)
1374	conn, client, err := getSftpClient(user)
1375	if assert.NoError(t, err) {
1376		defer conn.Close()
1377		defer client.Close()
1378		testFileName1 := "test_file1.dat"
1379		testFileSize := int64(131072)
1380		testFileSize1 := int64(65535)
1381		dir1 := "dir1"
1382		dir2 := "dir2"
1383		err = client.Mkdir(path.Join(vdirPath1, dir1))
1384		assert.NoError(t, err)
1385		err = client.Mkdir(path.Join(vdirPath1, dir2))
1386		assert.NoError(t, err)
1387		err = client.Mkdir(path.Join(vdirPath2, dir1))
1388		assert.NoError(t, err)
1389		err = client.Mkdir(path.Join(vdirPath2, dir2))
1390		assert.NoError(t, err)
1391		err = writeSFTPFile(path.Join(vdirPath1, dir1, testFileName), testFileSize, client)
1392		assert.NoError(t, err)
1393		err = writeSFTPFile(path.Join(vdirPath1, dir2, testFileName1), testFileSize1, client)
1394		assert.NoError(t, err)
1395		err = writeSFTPFile(path.Join(vdirPath2, dir1, testFileName), testFileSize, client)
1396		assert.NoError(t, err)
1397		err = writeSFTPFile(path.Join(vdirPath2, dir2, testFileName1), testFileSize1, client)
1398		assert.NoError(t, err)
1399		// initial files:
1400		// - vdir1/dir1/testFileName
1401		// - vdir1/dir2/testFileName1
1402		// - vdir2/dir1/testFileName
1403		// - vdir2/dir2/testFileName1
1404		//
1405		// rename a file from vdir1 to the user home dir, vdir1 is included in user quota so we have:
1406		// - testFileName
1407		// - vdir1/dir2/testFileName1
1408		// - vdir2/dir1/testFileName
1409		// - vdir2/dir2/testFileName1
1410		err = client.Rename(path.Join(vdirPath1, dir1, testFileName), path.Join(testFileName))
1411		assert.NoError(t, err)
1412		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1413		assert.NoError(t, err)
1414		assert.Equal(t, 2, user.UsedQuotaFiles)
1415		assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
1416		f, _, err := httpdtest.GetFolderByName(folderName1, http.StatusOK)
1417		assert.NoError(t, err)
1418		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
1419		assert.Equal(t, 1, f.UsedQuotaFiles)
1420		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
1421		assert.NoError(t, err)
1422		assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize)
1423		assert.Equal(t, 2, f.UsedQuotaFiles)
1424		// rename a file from vdir2 to the user home dir, vdir2 is not included in user quota so we have:
1425		// - testFileName
1426		// - testFileName1
1427		// - vdir1/dir2/testFileName1
1428		// - vdir2/dir1/testFileName
1429		err = client.Rename(path.Join(vdirPath2, dir2, testFileName1), path.Join(testFileName1))
1430		assert.NoError(t, err)
1431		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1432		assert.NoError(t, err)
1433		assert.Equal(t, 3, user.UsedQuotaFiles)
1434		assert.Equal(t, testFileSize+testFileSize1+testFileSize1, user.UsedQuotaSize)
1435		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
1436		assert.NoError(t, err)
1437		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
1438		assert.Equal(t, 1, f.UsedQuotaFiles)
1439		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
1440		assert.NoError(t, err)
1441		assert.Equal(t, testFileSize, f.UsedQuotaSize)
1442		assert.Equal(t, 1, f.UsedQuotaFiles)
1443		// rename a file from vdir1 to the user home dir overwriting an existing file, vdir1 is included in user quota so we have:
1444		// - testFileName (initial testFileName1)
1445		// - testFileName1
1446		// - vdir2/dir1/testFileName
1447		err = client.Rename(path.Join(vdirPath1, dir2, testFileName1), path.Join(testFileName))
1448		assert.NoError(t, err)
1449		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1450		assert.NoError(t, err)
1451		assert.Equal(t, 2, user.UsedQuotaFiles)
1452		assert.Equal(t, testFileSize1+testFileSize1, user.UsedQuotaSize)
1453		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
1454		assert.NoError(t, err)
1455		assert.Equal(t, int64(0), f.UsedQuotaSize)
1456		assert.Equal(t, 0, f.UsedQuotaFiles)
1457		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
1458		assert.NoError(t, err)
1459		assert.Equal(t, testFileSize, f.UsedQuotaSize)
1460		assert.Equal(t, 1, f.UsedQuotaFiles)
1461		// rename a file from vdir2 to the user home dir overwriting an existing file, vdir2 is not included in user quota so we have:
1462		// - testFileName (initial testFileName1)
1463		// - testFileName1 (initial testFileName)
1464		err = client.Rename(path.Join(vdirPath2, dir1, testFileName), path.Join(testFileName1))
1465		assert.NoError(t, err)
1466		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1467		assert.NoError(t, err)
1468		assert.Equal(t, 2, user.UsedQuotaFiles)
1469		assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
1470		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
1471		assert.NoError(t, err)
1472		assert.Equal(t, int64(0), f.UsedQuotaSize)
1473		assert.Equal(t, 0, f.UsedQuotaFiles)
1474		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
1475		assert.NoError(t, err)
1476		assert.Equal(t, int64(0), f.UsedQuotaSize)
1477		assert.Equal(t, 0, f.UsedQuotaFiles)
1478		// dir rename
1479		err = writeSFTPFile(path.Join(vdirPath1, dir1, testFileName), testFileSize, client)
1480		assert.NoError(t, err)
1481		err = writeSFTPFile(path.Join(vdirPath1, dir1, testFileName1), testFileSize1, client)
1482		assert.NoError(t, err)
1483		err = writeSFTPFile(path.Join(vdirPath2, dir1, testFileName), testFileSize, client)
1484		assert.NoError(t, err)
1485		err = writeSFTPFile(path.Join(vdirPath2, dir1, testFileName1), testFileSize1, client)
1486		assert.NoError(t, err)
1487		// - testFileName (initial testFileName1)
1488		// - testFileName1 (initial testFileName)
1489		// - vdir1/dir1/testFileName
1490		// - vdir1/dir1/testFileName1
1491		// - dir1/testFileName
1492		// - dir1/testFileName1
1493		err = client.Rename(path.Join(vdirPath2, dir1), dir1)
1494		assert.NoError(t, err)
1495		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1496		assert.NoError(t, err)
1497		assert.Equal(t, 6, user.UsedQuotaFiles)
1498		assert.Equal(t, testFileSize*3+testFileSize1*3, user.UsedQuotaSize)
1499		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
1500		assert.NoError(t, err)
1501		assert.Equal(t, testFileSize+testFileSize1, f.UsedQuotaSize)
1502		assert.Equal(t, 2, f.UsedQuotaFiles)
1503		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
1504		assert.NoError(t, err)
1505		assert.Equal(t, int64(0), f.UsedQuotaSize)
1506		assert.Equal(t, 0, f.UsedQuotaFiles)
1507		// - testFileName (initial testFileName1)
1508		// - testFileName1 (initial testFileName)
1509		// - dir2/testFileName
1510		// - dir2/testFileName1
1511		// - dir1/testFileName
1512		// - dir1/testFileName1
1513		err = client.Rename(path.Join(vdirPath1, dir1), dir2)
1514		assert.NoError(t, err)
1515		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1516		assert.NoError(t, err)
1517		assert.Equal(t, 6, user.UsedQuotaFiles)
1518		assert.Equal(t, testFileSize*3+testFileSize1*3, user.UsedQuotaSize)
1519		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
1520		assert.NoError(t, err)
1521		assert.Equal(t, int64(0), f.UsedQuotaSize)
1522		assert.Equal(t, 0, f.UsedQuotaFiles)
1523		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
1524		assert.NoError(t, err)
1525		assert.Equal(t, int64(0), f.UsedQuotaSize)
1526		assert.Equal(t, 0, f.UsedQuotaFiles)
1527	}
1528	_, err = httpdtest.RemoveUser(user, http.StatusOK)
1529	assert.NoError(t, err)
1530	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
1531	assert.NoError(t, err)
1532	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
1533	assert.NoError(t, err)
1534	err = os.RemoveAll(user.GetHomeDir())
1535	assert.NoError(t, err)
1536	err = os.RemoveAll(mappedPath1)
1537	assert.NoError(t, err)
1538	err = os.RemoveAll(mappedPath2)
1539	assert.NoError(t, err)
1540}
1541
1542func TestQuotaRenameToVirtualFolder(t *testing.T) {
1543	u := getTestUser()
1544	u.QuotaFiles = 100
1545	mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
1546	folderName1 := filepath.Base(mappedPath1)
1547	vdirPath1 := "/vdir1"
1548	mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
1549	folderName2 := filepath.Base(mappedPath2)
1550	vdirPath2 := "/vdir2"
1551	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
1552		BaseVirtualFolder: vfs.BaseVirtualFolder{
1553			Name:       folderName1,
1554			MappedPath: mappedPath1,
1555		},
1556		VirtualPath: vdirPath1,
1557		// quota is included in the user's one
1558		QuotaFiles: -1,
1559		QuotaSize:  -1,
1560	})
1561	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
1562		BaseVirtualFolder: vfs.BaseVirtualFolder{
1563			Name:       folderName2,
1564			MappedPath: mappedPath2,
1565		},
1566		VirtualPath: vdirPath2,
1567		// quota is unlimited and excluded from user's one
1568		QuotaFiles: 0,
1569		QuotaSize:  0,
1570	})
1571	u.Permissions[vdirPath1] = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermUpload,
1572		dataprovider.PermOverwrite, dataprovider.PermDelete, dataprovider.PermCreateSymlinks, dataprovider.PermCreateDirs}
1573	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
1574	assert.NoError(t, err)
1575	conn, client, err := getSftpClient(user)
1576	if assert.NoError(t, err) {
1577		defer conn.Close()
1578		defer client.Close()
1579		testFileName1 := "test_file1.dat"
1580		testFileSize := int64(131072)
1581		testFileSize1 := int64(65535)
1582		dir1 := "dir1"
1583		dir2 := "dir2"
1584		err = client.Mkdir(path.Join(vdirPath1, dir1))
1585		assert.NoError(t, err)
1586		err = client.Mkdir(path.Join(vdirPath1, dir2))
1587		assert.NoError(t, err)
1588		err = client.Mkdir(path.Join(vdirPath2, dir1))
1589		assert.NoError(t, err)
1590		err = client.Mkdir(path.Join(vdirPath2, dir2))
1591		assert.NoError(t, err)
1592		err = writeSFTPFile(testFileName, testFileSize, client)
1593		assert.NoError(t, err)
1594		err = writeSFTPFile(testFileName1, testFileSize1, client)
1595		assert.NoError(t, err)
1596		// initial files:
1597		// - testFileName
1598		// - testFileName1
1599		//
1600		// rename a file from user home dir to vdir1, vdir1 is included in user quota so we have:
1601		// - testFileName
1602		// - /vdir1/dir1/testFileName1
1603		err = client.Rename(testFileName1, path.Join(vdirPath1, dir1, testFileName1))
1604		assert.NoError(t, err)
1605		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1606		assert.NoError(t, err)
1607		assert.Equal(t, 2, user.UsedQuotaFiles)
1608		assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
1609		f, _, err := httpdtest.GetFolderByName(folderName1, http.StatusOK)
1610		assert.NoError(t, err)
1611		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
1612		assert.Equal(t, 1, f.UsedQuotaFiles)
1613		// rename a file from user home dir to vdir2, vdir2 is not included in user quota so we have:
1614		// - /vdir2/dir1/testFileName
1615		// - /vdir1/dir1/testFileName1
1616		err = client.Rename(testFileName, path.Join(vdirPath2, dir1, testFileName))
1617		assert.NoError(t, err)
1618		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1619		assert.NoError(t, err)
1620		assert.Equal(t, 1, user.UsedQuotaFiles)
1621		assert.Equal(t, testFileSize1, user.UsedQuotaSize)
1622		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
1623		assert.NoError(t, err)
1624		assert.Equal(t, testFileSize, f.UsedQuotaSize)
1625		assert.Equal(t, 1, f.UsedQuotaFiles)
1626		// upload two new files to the user home dir so we have:
1627		// - testFileName
1628		// - testFileName1
1629		// - /vdir1/dir1/testFileName1
1630		// - /vdir2/dir1/testFileName
1631		err = writeSFTPFile(testFileName, testFileSize, client)
1632		assert.NoError(t, err)
1633		err = writeSFTPFile(testFileName1, testFileSize1, client)
1634		assert.NoError(t, err)
1635		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1636		assert.NoError(t, err)
1637		assert.Equal(t, 3, user.UsedQuotaFiles)
1638		assert.Equal(t, testFileSize+testFileSize1+testFileSize1, user.UsedQuotaSize)
1639		// rename a file from user home dir to vdir1 overwriting an existing file, vdir1 is included in user quota so we have:
1640		// - testFileName1
1641		// - /vdir1/dir1/testFileName1 (initial testFileName)
1642		// - /vdir2/dir1/testFileName
1643		err = client.Rename(testFileName, path.Join(vdirPath1, dir1, testFileName1))
1644		assert.NoError(t, err)
1645		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1646		assert.NoError(t, err)
1647		assert.Equal(t, 2, user.UsedQuotaFiles)
1648		assert.Equal(t, testFileSize+testFileSize1, user.UsedQuotaSize)
1649		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
1650		assert.NoError(t, err)
1651		assert.Equal(t, testFileSize, f.UsedQuotaSize)
1652		assert.Equal(t, 1, f.UsedQuotaFiles)
1653		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
1654		assert.NoError(t, err)
1655		assert.Equal(t, testFileSize, f.UsedQuotaSize)
1656		assert.Equal(t, 1, f.UsedQuotaFiles)
1657		// rename a file from user home dir to vdir2 overwriting an existing file, vdir2 is not included in user quota so we have:
1658		// - /vdir1/dir1/testFileName1 (initial testFileName)
1659		// - /vdir2/dir1/testFileName (initial testFileName1)
1660		err = client.Rename(testFileName1, path.Join(vdirPath2, dir1, testFileName))
1661		assert.NoError(t, err)
1662		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1663		assert.NoError(t, err)
1664		assert.Equal(t, 1, user.UsedQuotaFiles)
1665		assert.Equal(t, testFileSize, user.UsedQuotaSize)
1666		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
1667		assert.NoError(t, err)
1668		assert.Equal(t, testFileSize, f.UsedQuotaSize)
1669		assert.Equal(t, 1, f.UsedQuotaFiles)
1670		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
1671		assert.NoError(t, err)
1672		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
1673		assert.Equal(t, 1, f.UsedQuotaFiles)
1674
1675		err = client.Mkdir(dir1)
1676		assert.NoError(t, err)
1677		err = writeSFTPFile(path.Join(dir1, testFileName), testFileSize, client)
1678		assert.NoError(t, err)
1679		err = writeSFTPFile(path.Join(dir1, testFileName1), testFileSize1, client)
1680		assert.NoError(t, err)
1681		// - /dir1/testFileName
1682		// - /dir1/testFileName1
1683		// - /vdir1/dir1/testFileName1 (initial testFileName)
1684		// - /vdir2/dir1/testFileName (initial testFileName1)
1685		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1686		assert.NoError(t, err)
1687		assert.Equal(t, 3, user.UsedQuotaFiles)
1688		assert.Equal(t, testFileSize*2+testFileSize1, user.UsedQuotaSize)
1689		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
1690		assert.NoError(t, err)
1691		assert.Equal(t, testFileSize, f.UsedQuotaSize)
1692		assert.Equal(t, 1, f.UsedQuotaFiles)
1693		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
1694		assert.NoError(t, err)
1695		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
1696		assert.Equal(t, 1, f.UsedQuotaFiles)
1697		// - /vdir1/adir/testFileName
1698		// - /vdir1/adir/testFileName1
1699		// - /vdir1/dir1/testFileName1 (initial testFileName)
1700		// - /vdir2/dir1/testFileName (initial testFileName1)
1701		err = client.Rename(dir1, path.Join(vdirPath1, "adir"))
1702		assert.NoError(t, err)
1703		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1704		assert.NoError(t, err)
1705		assert.Equal(t, 3, user.UsedQuotaFiles)
1706		assert.Equal(t, testFileSize*2+testFileSize1, user.UsedQuotaSize)
1707		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
1708		assert.NoError(t, err)
1709		assert.Equal(t, testFileSize*2+testFileSize1, f.UsedQuotaSize)
1710		assert.Equal(t, 3, f.UsedQuotaFiles)
1711		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
1712		assert.NoError(t, err)
1713		assert.Equal(t, testFileSize1, f.UsedQuotaSize)
1714		assert.Equal(t, 1, f.UsedQuotaFiles)
1715		err = client.Mkdir(dir1)
1716		assert.NoError(t, err)
1717		err = writeSFTPFile(path.Join(dir1, testFileName), testFileSize, client)
1718		assert.NoError(t, err)
1719		err = writeSFTPFile(path.Join(dir1, testFileName1), testFileSize1, client)
1720		assert.NoError(t, err)
1721		// - /vdir1/adir/testFileName
1722		// - /vdir1/adir/testFileName1
1723		// - /vdir1/dir1/testFileName1 (initial testFileName)
1724		// - /vdir2/dir1/testFileName (initial testFileName1)
1725		// - /vdir2/adir/testFileName
1726		// - /vdir2/adir/testFileName1
1727		err = client.Rename(dir1, path.Join(vdirPath2, "adir"))
1728		assert.NoError(t, err)
1729		user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
1730		assert.NoError(t, err)
1731		assert.Equal(t, 3, user.UsedQuotaFiles)
1732		assert.Equal(t, testFileSize*2+testFileSize1, user.UsedQuotaSize)
1733		f, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
1734		assert.NoError(t, err)
1735		assert.Equal(t, testFileSize*2+testFileSize1, f.UsedQuotaSize)
1736		assert.Equal(t, 3, f.UsedQuotaFiles)
1737		f, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
1738		assert.NoError(t, err)
1739		assert.Equal(t, testFileSize1*2+testFileSize, f.UsedQuotaSize)
1740		assert.Equal(t, 3, f.UsedQuotaFiles)
1741	}
1742	_, err = httpdtest.RemoveUser(user, http.StatusOK)
1743	assert.NoError(t, err)
1744	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
1745	assert.NoError(t, err)
1746	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
1747	assert.NoError(t, err)
1748	err = os.RemoveAll(user.GetHomeDir())
1749	assert.NoError(t, err)
1750	err = os.RemoveAll(mappedPath1)
1751	assert.NoError(t, err)
1752	err = os.RemoveAll(mappedPath2)
1753	assert.NoError(t, err)
1754}
1755
1756func TestVirtualFoldersLink(t *testing.T) {
1757	u := getTestUser()
1758	mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
1759	folderName1 := filepath.Base(mappedPath1)
1760	vdirPath1 := "/vdir1"
1761	mappedPath2 := filepath.Join(os.TempDir(), "vdir2")
1762	folderName2 := filepath.Base(mappedPath2)
1763	vdirPath2 := "/vdir2"
1764	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
1765		BaseVirtualFolder: vfs.BaseVirtualFolder{
1766			Name:       folderName1,
1767			MappedPath: mappedPath1,
1768		},
1769		VirtualPath: vdirPath1,
1770		// quota is included in the user's one
1771		QuotaFiles: -1,
1772		QuotaSize:  -1,
1773	})
1774	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
1775		BaseVirtualFolder: vfs.BaseVirtualFolder{
1776			Name:       folderName2,
1777			MappedPath: mappedPath2,
1778		},
1779		VirtualPath: vdirPath2,
1780		// quota is unlimited and excluded from user's one
1781		QuotaFiles: 0,
1782		QuotaSize:  0,
1783	})
1784	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
1785	assert.NoError(t, err)
1786	conn, client, err := getSftpClient(user)
1787	if assert.NoError(t, err) {
1788		defer conn.Close()
1789		defer client.Close()
1790		testFileSize := int64(131072)
1791		testDir := "adir"
1792		err = writeSFTPFile(testFileName, testFileSize, client)
1793		assert.NoError(t, err)
1794		err = writeSFTPFile(path.Join(vdirPath1, testFileName), testFileSize, client)
1795		assert.NoError(t, err)
1796		err = writeSFTPFile(path.Join(vdirPath2, testFileName), testFileSize, client)
1797		assert.NoError(t, err)
1798		err = client.Mkdir(path.Join(vdirPath1, testDir))
1799		assert.NoError(t, err)
1800		err = client.Mkdir(path.Join(vdirPath2, testDir))
1801		assert.NoError(t, err)
1802		err = client.Symlink(testFileName, testFileName+".link")
1803		assert.NoError(t, err)
1804		err = client.Symlink(path.Join(vdirPath1, testFileName), path.Join(vdirPath1, testFileName+".link"))
1805		assert.NoError(t, err)
1806		err = client.Symlink(path.Join(vdirPath1, testFileName), path.Join(vdirPath1, testDir, testFileName+".link"))
1807		assert.NoError(t, err)
1808		err = client.Symlink(path.Join(vdirPath2, testFileName), path.Join(vdirPath2, testFileName+".link"))
1809		assert.NoError(t, err)
1810		err = client.Symlink(path.Join(vdirPath2, testFileName), path.Join(vdirPath2, testDir, testFileName+".link"))
1811		assert.NoError(t, err)
1812		err = client.Symlink(testFileName, path.Join(vdirPath1, testFileName+".link1"))
1813		if assert.Error(t, err) {
1814			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
1815		}
1816		err = client.Symlink(testFileName, path.Join(vdirPath1, testDir, testFileName+".link1"))
1817		if assert.Error(t, err) {
1818			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
1819		}
1820		err = client.Symlink(testFileName, path.Join(vdirPath2, testFileName+".link1"))
1821		if assert.Error(t, err) {
1822			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
1823		}
1824		err = client.Symlink(testFileName, path.Join(vdirPath2, testDir, testFileName+".link1"))
1825		if assert.Error(t, err) {
1826			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
1827		}
1828		err = client.Symlink(path.Join(vdirPath1, testFileName), testFileName+".link1")
1829		if assert.Error(t, err) {
1830			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
1831		}
1832		err = client.Symlink(path.Join(vdirPath2, testFileName), testFileName+".link1")
1833		if assert.Error(t, err) {
1834			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
1835		}
1836		err = client.Symlink(path.Join(vdirPath1, testFileName), path.Join(vdirPath2, testDir, testFileName+".link1"))
1837		if assert.Error(t, err) {
1838			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
1839		}
1840		err = client.Symlink(path.Join(vdirPath2, testFileName), path.Join(vdirPath1, testFileName+".link1"))
1841		if assert.Error(t, err) {
1842			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
1843		}
1844		err = client.Symlink("/", "/roolink")
1845		assert.ErrorIs(t, err, os.ErrPermission)
1846		err = client.Symlink(testFileName, "/")
1847		assert.ErrorIs(t, err, os.ErrPermission)
1848		err = client.Symlink(testFileName, vdirPath1)
1849		if assert.Error(t, err) {
1850			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
1851		}
1852		err = client.Symlink(vdirPath1, testFileName+".link2")
1853		if assert.Error(t, err) {
1854			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
1855		}
1856	}
1857	_, err = httpdtest.RemoveUser(user, http.StatusOK)
1858	assert.NoError(t, err)
1859	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
1860	assert.NoError(t, err)
1861	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
1862	assert.NoError(t, err)
1863	err = os.RemoveAll(user.GetHomeDir())
1864	assert.NoError(t, err)
1865	err = os.RemoveAll(mappedPath1)
1866	assert.NoError(t, err)
1867	err = os.RemoveAll(mappedPath2)
1868	assert.NoError(t, err)
1869}
1870
1871func TestDirs(t *testing.T) {
1872	u := getTestUser()
1873	mappedPath := filepath.Join(os.TempDir(), "vdir")
1874	folderName := filepath.Base(mappedPath)
1875	vdirPath := "/path/vdir"
1876	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
1877		BaseVirtualFolder: vfs.BaseVirtualFolder{
1878			Name:       folderName,
1879			MappedPath: mappedPath,
1880		},
1881		VirtualPath: vdirPath,
1882	})
1883	u.Permissions["/subdir"] = []string{dataprovider.PermDownload, dataprovider.PermUpload,
1884		dataprovider.PermDelete, dataprovider.PermCreateDirs, dataprovider.PermRename, dataprovider.PermListItems}
1885
1886	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
1887	assert.NoError(t, err)
1888	conn, client, err := getSftpClient(user)
1889	if assert.NoError(t, err) {
1890		defer conn.Close()
1891		defer client.Close()
1892		info, err := client.ReadDir("/")
1893		if assert.NoError(t, err) {
1894			assert.Len(t, info, 1)
1895			assert.Equal(t, "path", info[0].Name())
1896		}
1897		fi, err := client.Stat(path.Dir(vdirPath))
1898		if assert.NoError(t, err) {
1899			assert.True(t, fi.IsDir())
1900		}
1901		err = client.RemoveDirectory("/")
1902		assert.ErrorIs(t, err, os.ErrPermission)
1903		err = client.RemoveDirectory(vdirPath)
1904		assert.ErrorIs(t, err, os.ErrPermission)
1905		err = client.RemoveDirectory(path.Dir(vdirPath))
1906		if assert.Error(t, err) {
1907			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
1908		}
1909		err = client.Mkdir(vdirPath)
1910		assert.ErrorIs(t, err, os.ErrPermission)
1911		err = client.Mkdir("adir")
1912		assert.NoError(t, err)
1913		err = client.Rename("/adir", path.Dir(vdirPath))
1914		if assert.Error(t, err) {
1915			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
1916		}
1917		err = client.MkdirAll("/subdir/adir")
1918		assert.NoError(t, err)
1919		err = client.Rename("adir", "subdir/adir")
1920		if assert.Error(t, err) {
1921			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
1922		}
1923		err = writeSFTPFile("/subdir/afile.bin", 64, client)
1924		assert.NoError(t, err)
1925		err = writeSFTPFile("/afile.bin", 32, client)
1926		assert.NoError(t, err)
1927		err = client.Rename("afile.bin", "subdir/afile.bin")
1928		assert.ErrorIs(t, err, os.ErrPermission)
1929		err = client.Rename("afile.bin", "subdir/afile1.bin")
1930		assert.NoError(t, err)
1931		err = client.Rename(path.Dir(vdirPath), "renamed_vdir")
1932		if assert.Error(t, err) {
1933			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
1934		}
1935	}
1936
1937	_, err = httpdtest.RemoveUser(user, http.StatusOK)
1938	assert.NoError(t, err)
1939	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName}, http.StatusOK)
1940	assert.NoError(t, err)
1941	err = os.RemoveAll(user.GetHomeDir())
1942	assert.NoError(t, err)
1943	err = os.RemoveAll(mappedPath)
1944	assert.NoError(t, err)
1945}
1946
1947func TestCryptFsStat(t *testing.T) {
1948	user, _, err := httpdtest.AddUser(getCryptFsUser(), http.StatusCreated)
1949	assert.NoError(t, err)
1950	conn, client, err := getSftpClient(user)
1951	if assert.NoError(t, err) {
1952		defer conn.Close()
1953		defer client.Close()
1954		testFileSize := int64(4096)
1955		err = writeSFTPFile(testFileName, testFileSize, client)
1956		assert.NoError(t, err)
1957		info, err := client.Stat(testFileName)
1958		if assert.NoError(t, err) {
1959			assert.Equal(t, testFileSize, info.Size())
1960		}
1961		info, err = os.Stat(filepath.Join(user.HomeDir, testFileName))
1962		if assert.NoError(t, err) {
1963			assert.Greater(t, info.Size(), testFileSize)
1964		}
1965	}
1966	_, err = httpdtest.RemoveUser(user, http.StatusOK)
1967	assert.NoError(t, err)
1968	err = os.RemoveAll(user.GetHomeDir())
1969	assert.NoError(t, err)
1970}
1971
1972func TestFsPermissionErrors(t *testing.T) {
1973	if runtime.GOOS == osWindows {
1974		t.Skip("this test is not available on Windows")
1975	}
1976	user, _, err := httpdtest.AddUser(getCryptFsUser(), http.StatusCreated)
1977	assert.NoError(t, err)
1978	conn, client, err := getSftpClient(user)
1979	if assert.NoError(t, err) {
1980		defer conn.Close()
1981		defer client.Close()
1982		testDir := "tDir"
1983		err = client.Mkdir(testDir)
1984		assert.NoError(t, err)
1985		err = os.Chmod(user.GetHomeDir(), 0111)
1986		assert.NoError(t, err)
1987
1988		err = client.RemoveDirectory(testDir)
1989		assert.ErrorIs(t, err, os.ErrPermission)
1990		err = client.Rename(testDir, testDir+"1")
1991		assert.ErrorIs(t, err, os.ErrPermission)
1992
1993		err = os.Chmod(user.GetHomeDir(), os.ModePerm)
1994		assert.NoError(t, err)
1995	}
1996	_, err = httpdtest.RemoveUser(user, http.StatusOK)
1997	assert.NoError(t, err)
1998	err = os.RemoveAll(user.GetHomeDir())
1999	assert.NoError(t, err)
2000}
2001
2002func TestResolvePathError(t *testing.T) {
2003	u := getTestUser()
2004	u.HomeDir = "relative_path"
2005	conn := common.NewBaseConnection("", common.ProtocolFTP, "", "", u)
2006	testPath := "apath"
2007	_, err := conn.ListDir(testPath)
2008	assert.Error(t, err)
2009	err = conn.CreateDir(testPath)
2010	assert.Error(t, err)
2011	err = conn.RemoveDir(testPath)
2012	assert.Error(t, err)
2013	err = conn.Rename(testPath, testPath+"1")
2014	assert.Error(t, err)
2015	err = conn.CreateSymlink(testPath, testPath+".sym")
2016	assert.Error(t, err)
2017	_, err = conn.DoStat(testPath, 0)
2018	assert.Error(t, err)
2019	err = conn.SetStat(testPath, &common.StatAttributes{
2020		Atime: time.Now(),
2021		Mtime: time.Now(),
2022	})
2023	assert.Error(t, err)
2024
2025	u = getTestUser()
2026	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
2027		BaseVirtualFolder: vfs.BaseVirtualFolder{
2028			MappedPath: "relative_mapped_path",
2029		},
2030		VirtualPath: "/vpath",
2031	})
2032	err = os.MkdirAll(u.HomeDir, os.ModePerm)
2033	assert.NoError(t, err)
2034	conn.User = u
2035	err = conn.Rename(testPath, "/vpath/subpath")
2036	assert.Error(t, err)
2037
2038	outHomePath := filepath.Join(os.TempDir(), testFileName)
2039	err = os.WriteFile(outHomePath, testFileContent, os.ModePerm)
2040	assert.NoError(t, err)
2041	err = os.Symlink(outHomePath, filepath.Join(u.HomeDir, testFileName+".link"))
2042	assert.NoError(t, err)
2043	err = os.WriteFile(filepath.Join(u.HomeDir, testFileName), testFileContent, os.ModePerm)
2044	assert.NoError(t, err)
2045	err = conn.CreateSymlink(testFileName, testFileName+".link")
2046	assert.Error(t, err)
2047
2048	err = os.RemoveAll(u.GetHomeDir())
2049	assert.NoError(t, err)
2050	err = os.Remove(outHomePath)
2051	assert.NoError(t, err)
2052}
2053
2054func TestUserPasswordHashing(t *testing.T) {
2055	if config.GetProviderConf().Driver == dataprovider.MemoryDataProviderName {
2056		t.Skip("this test is not supported with the memory provider")
2057	}
2058	u := getTestUser()
2059	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
2060	assert.NoError(t, err)
2061
2062	err = dataprovider.Close()
2063	assert.NoError(t, err)
2064	err = config.LoadConfig(configDir, "")
2065	assert.NoError(t, err)
2066	providerConf := config.GetProviderConf()
2067	providerConf.PasswordHashing.Algo = dataprovider.HashingAlgoArgon2ID
2068	err = dataprovider.Initialize(providerConf, configDir, true)
2069	assert.NoError(t, err)
2070
2071	currentUser, err := dataprovider.UserExists(user.Username)
2072	assert.NoError(t, err)
2073	assert.True(t, strings.HasPrefix(currentUser.Password, "$2a$"))
2074
2075	conn, client, err := getSftpClient(user)
2076	if assert.NoError(t, err) {
2077		defer conn.Close()
2078		defer client.Close()
2079		err = checkBasicSFTP(client)
2080		assert.NoError(t, err)
2081	}
2082
2083	_, err = httpdtest.RemoveUser(user, http.StatusOK)
2084	assert.NoError(t, err)
2085	err = os.RemoveAll(user.GetHomeDir())
2086	assert.NoError(t, err)
2087
2088	u = getTestUser()
2089	user, _, err = httpdtest.AddUser(u, http.StatusCreated)
2090	assert.NoError(t, err)
2091
2092	currentUser, err = dataprovider.UserExists(user.Username)
2093	assert.NoError(t, err)
2094	assert.True(t, strings.HasPrefix(currentUser.Password, "$argon2id$"))
2095
2096	conn, client, err = getSftpClient(user)
2097	if assert.NoError(t, err) {
2098		defer conn.Close()
2099		defer client.Close()
2100		err = checkBasicSFTP(client)
2101		assert.NoError(t, err)
2102	}
2103
2104	_, err = httpdtest.RemoveUser(user, http.StatusOK)
2105	assert.NoError(t, err)
2106	err = os.RemoveAll(user.GetHomeDir())
2107	assert.NoError(t, err)
2108
2109	err = dataprovider.Close()
2110	assert.NoError(t, err)
2111	err = config.LoadConfig(configDir, "")
2112	assert.NoError(t, err)
2113	providerConf = config.GetProviderConf()
2114	err = dataprovider.Initialize(providerConf, configDir, true)
2115	assert.NoError(t, err)
2116}
2117
2118func TestDelayedQuotaUpdater(t *testing.T) {
2119	err := dataprovider.Close()
2120	assert.NoError(t, err)
2121	err = config.LoadConfig(configDir, "")
2122	assert.NoError(t, err)
2123	providerConf := config.GetProviderConf()
2124	providerConf.DelayedQuotaUpdate = 120
2125	err = dataprovider.Initialize(providerConf, configDir, true)
2126	assert.NoError(t, err)
2127
2128	u := getTestUser()
2129	u.QuotaFiles = 100
2130	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
2131	assert.NoError(t, err)
2132
2133	err = dataprovider.UpdateUserQuota(&user, 10, 6000, false)
2134	assert.NoError(t, err)
2135	files, size, err := dataprovider.GetUsedQuota(user.Username)
2136	assert.NoError(t, err)
2137	assert.Equal(t, 10, files)
2138	assert.Equal(t, int64(6000), size)
2139
2140	userGet, err := dataprovider.UserExists(user.Username)
2141	assert.NoError(t, err)
2142	assert.Equal(t, 0, userGet.UsedQuotaFiles)
2143	assert.Equal(t, int64(0), userGet.UsedQuotaSize)
2144
2145	err = dataprovider.UpdateUserQuota(&user, 10, 6000, true)
2146	assert.NoError(t, err)
2147	files, size, err = dataprovider.GetUsedQuota(user.Username)
2148	assert.NoError(t, err)
2149	assert.Equal(t, 10, files)
2150	assert.Equal(t, int64(6000), size)
2151
2152	userGet, err = dataprovider.UserExists(user.Username)
2153	assert.NoError(t, err)
2154	assert.Equal(t, 10, userGet.UsedQuotaFiles)
2155	assert.Equal(t, int64(6000), userGet.UsedQuotaSize)
2156
2157	_, err = httpdtest.RemoveUser(user, http.StatusOK)
2158	assert.NoError(t, err)
2159	err = os.RemoveAll(user.GetHomeDir())
2160	assert.NoError(t, err)
2161
2162	folder := vfs.BaseVirtualFolder{
2163		Name:       "folder",
2164		MappedPath: filepath.Join(os.TempDir(), "p"),
2165	}
2166	err = dataprovider.AddFolder(&folder)
2167	assert.NoError(t, err)
2168
2169	err = dataprovider.UpdateVirtualFolderQuota(&folder, 10, 6000, false)
2170	assert.NoError(t, err)
2171	files, size, err = dataprovider.GetUsedVirtualFolderQuota(folder.Name)
2172	assert.NoError(t, err)
2173	assert.Equal(t, 10, files)
2174	assert.Equal(t, int64(6000), size)
2175
2176	folderGet, err := dataprovider.GetFolderByName(folder.Name)
2177	assert.NoError(t, err)
2178	assert.Equal(t, 0, folderGet.UsedQuotaFiles)
2179	assert.Equal(t, int64(0), folderGet.UsedQuotaSize)
2180
2181	err = dataprovider.UpdateVirtualFolderQuota(&folder, 10, 6000, true)
2182	assert.NoError(t, err)
2183	files, size, err = dataprovider.GetUsedVirtualFolderQuota(folder.Name)
2184	assert.NoError(t, err)
2185	assert.Equal(t, 10, files)
2186	assert.Equal(t, int64(6000), size)
2187
2188	folderGet, err = dataprovider.GetFolderByName(folder.Name)
2189	assert.NoError(t, err)
2190	assert.Equal(t, 10, folderGet.UsedQuotaFiles)
2191	assert.Equal(t, int64(6000), folderGet.UsedQuotaSize)
2192
2193	err = dataprovider.DeleteFolder(folder.Name, "", "")
2194	assert.NoError(t, err)
2195
2196	err = dataprovider.Close()
2197	assert.NoError(t, err)
2198	err = config.LoadConfig(configDir, "")
2199	assert.NoError(t, err)
2200	providerConf = config.GetProviderConf()
2201	err = dataprovider.Initialize(providerConf, configDir, true)
2202	assert.NoError(t, err)
2203}
2204
2205func TestPasswordCaching(t *testing.T) {
2206	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
2207	assert.NoError(t, err)
2208	found, match := dataprovider.CheckCachedPassword(user.Username, defaultPassword)
2209	assert.False(t, found)
2210	assert.False(t, match)
2211
2212	user.Password = "wrong"
2213	_, _, err = getSftpClient(user)
2214	assert.Error(t, err)
2215	found, match = dataprovider.CheckCachedPassword(user.Username, defaultPassword)
2216	assert.False(t, found)
2217	assert.False(t, match)
2218	user.Password = ""
2219
2220	conn, client, err := getSftpClient(user)
2221	if assert.NoError(t, err) {
2222		defer conn.Close()
2223		defer client.Close()
2224		err = checkBasicSFTP(client)
2225		assert.NoError(t, err)
2226	}
2227	found, match = dataprovider.CheckCachedPassword(user.Username, defaultPassword)
2228	assert.True(t, found)
2229	assert.True(t, match)
2230
2231	found, match = dataprovider.CheckCachedPassword(user.Username, defaultPassword+"_")
2232	assert.True(t, found)
2233	assert.False(t, match)
2234
2235	found, match = dataprovider.CheckCachedPassword(user.Username+"_", defaultPassword)
2236	assert.False(t, found)
2237	assert.False(t, match)
2238
2239	user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
2240	assert.NoError(t, err)
2241	found, match = dataprovider.CheckCachedPassword(user.Username, defaultPassword)
2242	assert.False(t, found)
2243	assert.False(t, match)
2244
2245	conn, client, err = getSftpClient(user)
2246	if assert.NoError(t, err) {
2247		defer conn.Close()
2248		defer client.Close()
2249		err = checkBasicSFTP(client)
2250		assert.NoError(t, err)
2251	}
2252
2253	found, match = dataprovider.CheckCachedPassword(user.Username, defaultPassword)
2254	assert.True(t, found)
2255	assert.True(t, match)
2256
2257	_, err = httpdtest.RemoveUser(user, http.StatusOK)
2258	assert.NoError(t, err)
2259	err = os.RemoveAll(user.GetHomeDir())
2260	assert.NoError(t, err)
2261	found, match = dataprovider.CheckCachedPassword(user.Username, defaultPassword)
2262	assert.False(t, found)
2263	assert.False(t, match)
2264}
2265
2266func TestSyncUploadAction(t *testing.T) {
2267	if runtime.GOOS == osWindows {
2268		t.Skip("this test is not available on Windows")
2269	}
2270	uploadScriptPath := filepath.Join(os.TempDir(), "upload.sh")
2271	common.Config.Actions.ExecuteOn = []string{"upload"}
2272	common.Config.Actions.ExecuteSync = []string{"upload"}
2273	common.Config.Actions.Hook = uploadScriptPath
2274
2275	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
2276	assert.NoError(t, err)
2277	movedPath := filepath.Join(user.HomeDir, "moved.dat")
2278	err = os.WriteFile(uploadScriptPath, getUploadScriptContent(movedPath), 0755)
2279	assert.NoError(t, err)
2280	conn, client, err := getSftpClient(user)
2281	if assert.NoError(t, err) {
2282		defer conn.Close()
2283		defer client.Close()
2284
2285		size := int64(32768)
2286		err = writeSFTPFileNoCheck(testFileName, size, client)
2287		assert.NoError(t, err)
2288		_, err = client.Stat(testFileName)
2289		assert.Error(t, err)
2290		info, err := client.Stat(filepath.Base(movedPath))
2291		if assert.NoError(t, err) {
2292			assert.Equal(t, size, info.Size())
2293		}
2294	}
2295
2296	err = os.Remove(uploadScriptPath)
2297	assert.NoError(t, err)
2298	_, err = httpdtest.RemoveUser(user, http.StatusOK)
2299	assert.NoError(t, err)
2300	err = os.RemoveAll(user.GetHomeDir())
2301	assert.NoError(t, err)
2302
2303	common.Config.Actions.ExecuteOn = nil
2304	common.Config.Actions.ExecuteSync = nil
2305	common.Config.Actions.Hook = uploadScriptPath
2306}
2307
2308func TestQuotaTrackDisabled(t *testing.T) {
2309	err := dataprovider.Close()
2310	assert.NoError(t, err)
2311	err = config.LoadConfig(configDir, "")
2312	assert.NoError(t, err)
2313	providerConf := config.GetProviderConf()
2314	providerConf.TrackQuota = 0
2315	err = dataprovider.Initialize(providerConf, configDir, true)
2316	assert.NoError(t, err)
2317
2318	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
2319	assert.NoError(t, err)
2320	conn, client, err := getSftpClient(user)
2321	if assert.NoError(t, err) {
2322		defer conn.Close()
2323		defer client.Close()
2324		err = writeSFTPFile(testFileName, 32, client)
2325		assert.NoError(t, err)
2326		err = client.Rename(testFileName, testFileName+"1")
2327		assert.NoError(t, err)
2328	}
2329	_, err = httpdtest.RemoveUser(user, http.StatusOK)
2330	assert.NoError(t, err)
2331	err = os.RemoveAll(user.GetHomeDir())
2332	assert.NoError(t, err)
2333
2334	err = dataprovider.Close()
2335	assert.NoError(t, err)
2336	err = config.LoadConfig(configDir, "")
2337	assert.NoError(t, err)
2338	providerConf = config.GetProviderConf()
2339	err = dataprovider.Initialize(providerConf, configDir, true)
2340	assert.NoError(t, err)
2341}
2342
2343func TestGetQuotaError(t *testing.T) {
2344	if dataprovider.GetProviderStatus().Driver == "memory" {
2345		t.Skip("this test is not available with the memory provider")
2346	}
2347	u := getTestUser()
2348	mappedPath := filepath.Join(os.TempDir(), "vdir")
2349	folderName := filepath.Base(mappedPath)
2350	vdirPath := "/vpath"
2351	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
2352		BaseVirtualFolder: vfs.BaseVirtualFolder{
2353			Name:       folderName,
2354			MappedPath: mappedPath,
2355		},
2356		VirtualPath: vdirPath,
2357		QuotaSize:   0,
2358		QuotaFiles:  10,
2359	})
2360	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
2361	assert.NoError(t, err)
2362	conn, client, err := getSftpClient(user)
2363	if assert.NoError(t, err) {
2364		defer conn.Close()
2365		defer client.Close()
2366		err = writeSFTPFile(testFileName, 32, client)
2367		assert.NoError(t, err)
2368
2369		err = dataprovider.Close()
2370		assert.NoError(t, err)
2371
2372		err = client.Rename(testFileName, path.Join(vdirPath, testFileName))
2373		assert.Error(t, err)
2374
2375		err = config.LoadConfig(configDir, "")
2376		assert.NoError(t, err)
2377		providerConf := config.GetProviderConf()
2378		err = dataprovider.Initialize(providerConf, configDir, true)
2379		assert.NoError(t, err)
2380	}
2381	_, err = httpdtest.RemoveUser(user, http.StatusOK)
2382	assert.NoError(t, err)
2383	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName}, http.StatusOK)
2384	assert.NoError(t, err)
2385	err = os.RemoveAll(user.GetHomeDir())
2386	assert.NoError(t, err)
2387	err = os.RemoveAll(mappedPath)
2388	assert.NoError(t, err)
2389}
2390
2391func TestRetentionAPI(t *testing.T) {
2392	u := getTestUser()
2393	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
2394	assert.NoError(t, err)
2395	uploadPath := path.Join(testDir, testFileName)
2396
2397	conn, client, err := getSftpClient(user)
2398	if assert.NoError(t, err) {
2399		defer conn.Close()
2400		defer client.Close()
2401
2402		err = client.Mkdir(testDir)
2403		assert.NoError(t, err)
2404		err = writeSFTPFile(uploadPath, 32, client)
2405		assert.NoError(t, err)
2406
2407		folderRetention := []common.FolderRetention{
2408			{
2409				Path:            "/",
2410				Retention:       24,
2411				DeleteEmptyDirs: true,
2412			},
2413		}
2414		_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
2415		assert.NoError(t, err)
2416
2417		assert.Eventually(t, func() bool {
2418			return len(common.RetentionChecks.Get()) == 0
2419		}, 1000*time.Millisecond, 50*time.Millisecond)
2420
2421		_, err = client.Stat(uploadPath)
2422		assert.NoError(t, err)
2423
2424		err = client.Chtimes(uploadPath, time.Now().Add(-48*time.Hour), time.Now().Add(-48*time.Hour))
2425		assert.NoError(t, err)
2426
2427		_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
2428		assert.NoError(t, err)
2429
2430		assert.Eventually(t, func() bool {
2431			return len(common.RetentionChecks.Get()) == 0
2432		}, 1000*time.Millisecond, 50*time.Millisecond)
2433
2434		_, err = client.Stat(uploadPath)
2435		assert.ErrorIs(t, err, os.ErrNotExist)
2436
2437		_, err = client.Stat(testDir)
2438		assert.ErrorIs(t, err, os.ErrNotExist)
2439
2440		err = client.Mkdir(testDir)
2441		assert.NoError(t, err)
2442		err = writeSFTPFile(uploadPath, 32, client)
2443		assert.NoError(t, err)
2444
2445		folderRetention[0].DeleteEmptyDirs = false
2446		err = client.Chtimes(uploadPath, time.Now().Add(-48*time.Hour), time.Now().Add(-48*time.Hour))
2447		assert.NoError(t, err)
2448
2449		_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
2450		assert.NoError(t, err)
2451
2452		assert.Eventually(t, func() bool {
2453			return len(common.RetentionChecks.Get()) == 0
2454		}, 1000*time.Millisecond, 50*time.Millisecond)
2455
2456		_, err = client.Stat(uploadPath)
2457		assert.ErrorIs(t, err, os.ErrNotExist)
2458
2459		_, err = client.Stat(testDir)
2460		assert.NoError(t, err)
2461
2462		err = writeSFTPFile(uploadPath, 32, client)
2463		assert.NoError(t, err)
2464		err = client.Chtimes(uploadPath, time.Now().Add(-48*time.Hour), time.Now().Add(-48*time.Hour))
2465		assert.NoError(t, err)
2466	}
2467
2468	// remove delete permissions to the user
2469	user.Permissions["/"+testDir] = []string{dataprovider.PermListItems, dataprovider.PermUpload,
2470		dataprovider.PermCreateDirs, dataprovider.PermChtimes}
2471	user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
2472	assert.NoError(t, err)
2473
2474	conn, client, err = getSftpClient(user)
2475	if assert.NoError(t, err) {
2476		defer conn.Close()
2477		defer client.Close()
2478
2479		innerUploadFilePath := path.Join("/"+testDir, testDir, testFileName)
2480		err = client.Mkdir(path.Join(testDir, testDir))
2481		assert.NoError(t, err)
2482
2483		err = writeSFTPFile(innerUploadFilePath, 32, client)
2484		assert.NoError(t, err)
2485		err = client.Chtimes(innerUploadFilePath, time.Now().Add(-48*time.Hour), time.Now().Add(-48*time.Hour))
2486		assert.NoError(t, err)
2487
2488		folderRetention := []common.FolderRetention{
2489			{
2490				Path:      "/missing",
2491				Retention: 24,
2492			},
2493			{
2494				Path:            "/" + testDir,
2495				Retention:       24,
2496				DeleteEmptyDirs: true,
2497			},
2498			{
2499				Path:                  path.Dir(innerUploadFilePath),
2500				Retention:             0,
2501				IgnoreUserPermissions: true,
2502			},
2503		}
2504		_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
2505		assert.NoError(t, err)
2506
2507		assert.Eventually(t, func() bool {
2508			return len(common.RetentionChecks.Get()) == 0
2509		}, 1000*time.Millisecond, 50*time.Millisecond)
2510
2511		_, err = client.Stat(uploadPath)
2512		assert.NoError(t, err)
2513		_, err = client.Stat(innerUploadFilePath)
2514		assert.NoError(t, err)
2515
2516		folderRetention[1].IgnoreUserPermissions = true
2517		_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
2518		assert.NoError(t, err)
2519
2520		assert.Eventually(t, func() bool {
2521			return len(common.RetentionChecks.Get()) == 0
2522		}, 1000*time.Millisecond, 50*time.Millisecond)
2523
2524		_, err = client.Stat(uploadPath)
2525		assert.ErrorIs(t, err, os.ErrNotExist)
2526		_, err = client.Stat(innerUploadFilePath)
2527		assert.NoError(t, err)
2528
2529		folderRetention = []common.FolderRetention{
2530
2531			{
2532				Path:                  "/" + testDir,
2533				Retention:             24,
2534				DeleteEmptyDirs:       true,
2535				IgnoreUserPermissions: true,
2536			},
2537		}
2538
2539		_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
2540		assert.NoError(t, err)
2541
2542		assert.Eventually(t, func() bool {
2543			return len(common.RetentionChecks.Get()) == 0
2544		}, 1000*time.Millisecond, 50*time.Millisecond)
2545
2546		_, err = client.Stat(innerUploadFilePath)
2547		assert.ErrorIs(t, err, os.ErrNotExist)
2548	}
2549	// finally test some errors removing files or folders
2550	if runtime.GOOS != osWindows {
2551		dirPath := filepath.Join(user.HomeDir, "adir", "sub")
2552		err := os.MkdirAll(dirPath, os.ModePerm)
2553		assert.NoError(t, err)
2554		filePath := filepath.Join(dirPath, "f.dat")
2555		err = os.WriteFile(filePath, nil, os.ModePerm)
2556		assert.NoError(t, err)
2557
2558		err = os.Chtimes(filePath, time.Now().Add(-72*time.Hour), time.Now().Add(-72*time.Hour))
2559		assert.NoError(t, err)
2560
2561		err = os.Chmod(dirPath, 0001)
2562		assert.NoError(t, err)
2563
2564		folderRetention := []common.FolderRetention{
2565
2566			{
2567				Path:                  "/adir",
2568				Retention:             24,
2569				DeleteEmptyDirs:       true,
2570				IgnoreUserPermissions: true,
2571			},
2572		}
2573
2574		_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
2575		assert.NoError(t, err)
2576
2577		assert.Eventually(t, func() bool {
2578			return len(common.RetentionChecks.Get()) == 0
2579		}, 1000*time.Millisecond, 50*time.Millisecond)
2580
2581		err = os.Chmod(dirPath, 0555)
2582		assert.NoError(t, err)
2583
2584		_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
2585		assert.NoError(t, err)
2586
2587		assert.Eventually(t, func() bool {
2588			return len(common.RetentionChecks.Get()) == 0
2589		}, 1000*time.Millisecond, 50*time.Millisecond)
2590
2591		err = os.Chmod(dirPath, os.ModePerm)
2592		assert.NoError(t, err)
2593
2594		_, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
2595		assert.NoError(t, err)
2596
2597		assert.Eventually(t, func() bool {
2598			return len(common.RetentionChecks.Get()) == 0
2599		}, 1000*time.Millisecond, 50*time.Millisecond)
2600
2601		assert.NoDirExists(t, dirPath)
2602	}
2603
2604	_, err = httpdtest.RemoveUser(user, http.StatusOK)
2605	assert.NoError(t, err)
2606	err = os.RemoveAll(user.GetHomeDir())
2607	assert.NoError(t, err)
2608}
2609
2610func TestRenameDir(t *testing.T) {
2611	u := getTestUser()
2612	testDir := "/dir-to-rename"
2613	u.Permissions[testDir] = []string{dataprovider.PermListItems, dataprovider.PermUpload}
2614	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
2615	assert.NoError(t, err)
2616	conn, client, err := getSftpClient(user)
2617	if assert.NoError(t, err) {
2618		defer conn.Close()
2619		defer client.Close()
2620		err = client.Mkdir(testDir)
2621		assert.NoError(t, err)
2622		err = writeSFTPFile(path.Join(testDir, testFileName), 32, client)
2623		assert.NoError(t, err)
2624		err = client.Rename(testDir, testDir+"_rename")
2625		assert.ErrorIs(t, err, os.ErrPermission)
2626	}
2627	_, err = httpdtest.RemoveUser(user, http.StatusOK)
2628	assert.NoError(t, err)
2629	err = os.RemoveAll(user.GetHomeDir())
2630	assert.NoError(t, err)
2631}
2632
2633func TestBuiltinKeyboardInteractiveAuthentication(t *testing.T) {
2634	u := getTestUser()
2635	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
2636	assert.NoError(t, err)
2637	authMethods := []ssh.AuthMethod{
2638		ssh.KeyboardInteractive(func(user, instruction string, questions []string, echos []bool) ([]string, error) {
2639			return []string{defaultPassword}, nil
2640		}),
2641	}
2642	conn, client, err := getCustomAuthSftpClient(user, authMethods)
2643	if assert.NoError(t, err) {
2644		defer conn.Close()
2645		defer client.Close()
2646		assert.NoError(t, checkBasicSFTP(client))
2647		err = writeSFTPFile(testFileName, 4096, client)
2648		assert.NoError(t, err)
2649	}
2650	// add multi-factor authentication
2651	configName, _, secret, _, err := mfa.GenerateTOTPSecret(mfa.GetAvailableTOTPConfigNames()[0], user.Username)
2652	assert.NoError(t, err)
2653	user.Password = defaultPassword
2654	user.Filters.TOTPConfig = sdk.TOTPConfig{
2655		Enabled:    true,
2656		ConfigName: configName,
2657		Secret:     kms.NewPlainSecret(secret),
2658		Protocols:  []string{common.ProtocolSSH},
2659	}
2660	err = dataprovider.UpdateUser(&user, "", "")
2661	assert.NoError(t, err)
2662	passcode, err := generateTOTPPasscode(secret, otp.AlgorithmSHA1)
2663	assert.NoError(t, err)
2664	passwordAsked := false
2665	passcodeAsked := false
2666	authMethods = []ssh.AuthMethod{
2667		ssh.KeyboardInteractive(func(user, instruction string, questions []string, echos []bool) ([]string, error) {
2668			var answers []string
2669			if strings.HasPrefix(questions[0], "Password") {
2670				answers = append(answers, defaultPassword)
2671				passwordAsked = true
2672			} else {
2673				answers = append(answers, passcode)
2674				passcodeAsked = true
2675			}
2676			return answers, nil
2677		}),
2678	}
2679	conn, client, err = getCustomAuthSftpClient(user, authMethods)
2680	if assert.NoError(t, err) {
2681		defer conn.Close()
2682		defer client.Close()
2683		assert.NoError(t, checkBasicSFTP(client))
2684		err = writeSFTPFile(testFileName, 4096, client)
2685		assert.NoError(t, err)
2686	}
2687	assert.True(t, passwordAsked)
2688	assert.True(t, passcodeAsked)
2689
2690	_, err = httpdtest.RemoveUser(user, http.StatusOK)
2691	assert.NoError(t, err)
2692	err = os.RemoveAll(user.GetHomeDir())
2693	assert.NoError(t, err)
2694}
2695
2696func TestRenameSymlink(t *testing.T) {
2697	u := getTestUser()
2698	testDir := "/dir-no-create-links"
2699	otherDir := "otherdir"
2700	u.Permissions[testDir] = []string{dataprovider.PermListItems, dataprovider.PermUpload, dataprovider.PermDelete,
2701		dataprovider.PermCreateDirs}
2702	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
2703	assert.NoError(t, err)
2704	conn, client, err := getSftpClient(user)
2705	if assert.NoError(t, err) {
2706		defer conn.Close()
2707		defer client.Close()
2708		err = client.Mkdir(otherDir)
2709		assert.NoError(t, err)
2710		err = client.Symlink(otherDir, otherDir+".link")
2711		assert.NoError(t, err)
2712		err = client.Rename(otherDir+".link", path.Join(testDir, "symlink"))
2713		assert.ErrorIs(t, err, os.ErrPermission)
2714		err = client.Rename(otherDir+".link", "allowed_link")
2715		assert.NoError(t, err)
2716	}
2717	_, err = httpdtest.RemoveUser(user, http.StatusOK)
2718	assert.NoError(t, err)
2719	err = os.RemoveAll(user.GetHomeDir())
2720	assert.NoError(t, err)
2721}
2722
2723func TestSplittedDeletePerms(t *testing.T) {
2724	u := getTestUser()
2725	u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermUpload, dataprovider.PermDeleteDirs,
2726		dataprovider.PermCreateDirs}
2727	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
2728	assert.NoError(t, err)
2729	conn, client, err := getSftpClient(user)
2730	if assert.NoError(t, err) {
2731		defer conn.Close()
2732		defer client.Close()
2733		err = writeSFTPFile(testFileName, 4096, client)
2734		assert.NoError(t, err)
2735		err = client.Remove(testFileName)
2736		assert.Error(t, err)
2737		err = client.Mkdir(testDir)
2738		assert.NoError(t, err)
2739		err = client.RemoveDirectory(testDir)
2740		assert.NoError(t, err)
2741	}
2742	u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermUpload, dataprovider.PermDeleteFiles,
2743		dataprovider.PermCreateDirs, dataprovider.PermOverwrite}
2744	_, _, err = httpdtest.UpdateUser(u, http.StatusOK, "")
2745	assert.NoError(t, err)
2746
2747	conn, client, err = getSftpClient(user)
2748	if assert.NoError(t, err) {
2749		defer conn.Close()
2750		defer client.Close()
2751		err = writeSFTPFile(testFileName, 4096, client)
2752		assert.NoError(t, err)
2753		err = client.Remove(testFileName)
2754		assert.NoError(t, err)
2755		err = client.Mkdir(testDir)
2756		assert.NoError(t, err)
2757		err = client.RemoveDirectory(testDir)
2758		assert.Error(t, err)
2759	}
2760	_, err = httpdtest.RemoveUser(user, http.StatusOK)
2761	assert.NoError(t, err)
2762	err = os.RemoveAll(user.GetHomeDir())
2763	assert.NoError(t, err)
2764}
2765
2766func TestSplittedRenamePerms(t *testing.T) {
2767	u := getTestUser()
2768	u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermUpload, dataprovider.PermRenameDirs,
2769		dataprovider.PermCreateDirs}
2770	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
2771	assert.NoError(t, err)
2772	conn, client, err := getSftpClient(user)
2773	if assert.NoError(t, err) {
2774		defer conn.Close()
2775		defer client.Close()
2776		err = writeSFTPFile(testFileName, 4096, client)
2777		assert.NoError(t, err)
2778		err = client.Mkdir(testDir)
2779		assert.NoError(t, err)
2780		err = client.Rename(testFileName, testFileName+"_renamed")
2781		assert.Error(t, err)
2782		err = client.Rename(testDir, testDir+"_renamed")
2783		assert.NoError(t, err)
2784	}
2785	u.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermUpload, dataprovider.PermRenameFiles,
2786		dataprovider.PermCreateDirs, dataprovider.PermOverwrite}
2787	_, _, err = httpdtest.UpdateUser(u, http.StatusOK, "")
2788	assert.NoError(t, err)
2789
2790	conn, client, err = getSftpClient(user)
2791	if assert.NoError(t, err) {
2792		defer conn.Close()
2793		defer client.Close()
2794		err = writeSFTPFile(testFileName, 4096, client)
2795		assert.NoError(t, err)
2796		err = client.Mkdir(testDir)
2797		assert.NoError(t, err)
2798		err = client.Rename(testFileName, testFileName+"_renamed")
2799		assert.NoError(t, err)
2800		err = client.Rename(testDir, testDir+"_renamed")
2801		assert.Error(t, err)
2802	}
2803	_, err = httpdtest.RemoveUser(user, http.StatusOK)
2804	assert.NoError(t, err)
2805	err = os.RemoveAll(user.GetHomeDir())
2806	assert.NoError(t, err)
2807}
2808
2809func TestSFTPLoopError(t *testing.T) {
2810	user1 := getTestUser()
2811	user2 := getTestUser()
2812	user1.Username += "1"
2813	user2.Username += "2"
2814	// user1 is a local account with a virtual SFTP folder to user2
2815	// user2 has user1 as SFTP fs
2816	user1.VirtualFolders = append(user1.VirtualFolders, vfs.VirtualFolder{
2817		BaseVirtualFolder: vfs.BaseVirtualFolder{
2818			Name: "sftp",
2819			FsConfig: vfs.Filesystem{
2820				Provider: sdk.SFTPFilesystemProvider,
2821				SFTPConfig: vfs.SFTPFsConfig{
2822					SFTPFsConfig: sdk.SFTPFsConfig{
2823						Endpoint: sftpServerAddr,
2824						Username: user2.Username,
2825						Password: kms.NewPlainSecret(defaultPassword),
2826					},
2827				},
2828			},
2829		},
2830		VirtualPath: "/vdir",
2831	})
2832
2833	user2.FsConfig.Provider = sdk.SFTPFilesystemProvider
2834	user2.FsConfig.SFTPConfig = vfs.SFTPFsConfig{
2835		SFTPFsConfig: sdk.SFTPFsConfig{
2836			Endpoint: sftpServerAddr,
2837			Username: user1.Username,
2838			Password: kms.NewPlainSecret(defaultPassword),
2839		},
2840	}
2841
2842	user1, resp, err := httpdtest.AddUser(user1, http.StatusCreated)
2843	assert.NoError(t, err, string(resp))
2844	user2, resp, err = httpdtest.AddUser(user2, http.StatusCreated)
2845	assert.NoError(t, err, string(resp))
2846
2847	user1.VirtualFolders[0].FsConfig.SFTPConfig.Password = kms.NewPlainSecret(defaultPassword)
2848	user2.FsConfig.SFTPConfig.Password = kms.NewPlainSecret(defaultPassword)
2849
2850	conn := common.NewBaseConnection("", common.ProtocolWebDAV, "", "", user1)
2851	_, _, err = conn.GetFsAndResolvedPath(user1.VirtualFolders[0].VirtualPath)
2852	assert.ErrorIs(t, err, os.ErrPermission)
2853
2854	conn = common.NewBaseConnection("", common.ProtocolSFTP, "", "", user1)
2855	_, _, err = conn.GetFsAndResolvedPath(user1.VirtualFolders[0].VirtualPath)
2856	if assert.Error(t, err) {
2857		assert.Contains(t, err.Error(), "SFTP loop")
2858	}
2859	conn = common.NewBaseConnection("", common.ProtocolFTP, "", "", user1)
2860	_, _, err = conn.GetFsAndResolvedPath(user1.VirtualFolders[0].VirtualPath)
2861	if assert.Error(t, err) {
2862		assert.Contains(t, err.Error(), "SFTP loop")
2863	}
2864	_, err = httpdtest.RemoveUser(user1, http.StatusOK)
2865	assert.NoError(t, err)
2866	err = os.RemoveAll(user1.GetHomeDir())
2867	assert.NoError(t, err)
2868	_, err = httpdtest.RemoveUser(user2, http.StatusOK)
2869	assert.NoError(t, err)
2870	err = os.RemoveAll(user2.GetHomeDir())
2871	assert.NoError(t, err)
2872	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: "sftp"}, http.StatusOK)
2873	assert.NoError(t, err)
2874}
2875
2876func TestNonLocalCrossRename(t *testing.T) {
2877	baseUser, resp, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
2878	assert.NoError(t, err, string(resp))
2879	u := getTestUser()
2880	u.HomeDir += "_folders"
2881	u.Username += "_folders"
2882	mappedPathSFTP := filepath.Join(os.TempDir(), "sftp")
2883	folderNameSFTP := filepath.Base(mappedPathSFTP)
2884	vdirSFTPPath := "/vdir/sftp"
2885	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
2886		BaseVirtualFolder: vfs.BaseVirtualFolder{
2887			Name: folderNameSFTP,
2888			FsConfig: vfs.Filesystem{
2889				Provider: sdk.SFTPFilesystemProvider,
2890				SFTPConfig: vfs.SFTPFsConfig{
2891					SFTPFsConfig: sdk.SFTPFsConfig{
2892						Endpoint: sftpServerAddr,
2893						Username: baseUser.Username,
2894						Password: kms.NewPlainSecret(defaultPassword),
2895					},
2896				},
2897			},
2898		},
2899		VirtualPath: vdirSFTPPath,
2900	})
2901	mappedPathCrypt := filepath.Join(os.TempDir(), "crypt")
2902	folderNameCrypt := filepath.Base(mappedPathCrypt)
2903	vdirCryptPath := "/vdir/crypt"
2904	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
2905		BaseVirtualFolder: vfs.BaseVirtualFolder{
2906			Name: folderNameCrypt,
2907			FsConfig: vfs.Filesystem{
2908				Provider: sdk.CryptedFilesystemProvider,
2909				CryptConfig: vfs.CryptFsConfig{
2910					CryptFsConfig: sdk.CryptFsConfig{
2911						Passphrase: kms.NewPlainSecret(defaultPassword),
2912					},
2913				},
2914			},
2915			MappedPath: mappedPathCrypt,
2916		},
2917		VirtualPath: vdirCryptPath,
2918	})
2919	user, resp, err := httpdtest.AddUser(u, http.StatusCreated)
2920	assert.NoError(t, err, string(resp))
2921	conn, client, err := getSftpClient(user)
2922	if assert.NoError(t, err) {
2923		defer conn.Close()
2924		defer client.Close()
2925		assert.NoError(t, checkBasicSFTP(client))
2926		err = writeSFTPFile(testFileName, 4096, client)
2927		assert.NoError(t, err)
2928		err = writeSFTPFile(path.Join(vdirSFTPPath, testFileName), 8192, client)
2929		assert.NoError(t, err)
2930		err = writeSFTPFile(path.Join(vdirCryptPath, testFileName), 16384, client)
2931		assert.NoError(t, err)
2932		err = client.Rename(path.Join(vdirSFTPPath, testFileName), path.Join(vdirCryptPath, testFileName+".rename"))
2933		assert.ErrorIs(t, err, os.ErrPermission)
2934		err = client.Rename(path.Join(vdirCryptPath, testFileName), path.Join(vdirSFTPPath, testFileName+".rename"))
2935		assert.ErrorIs(t, err, os.ErrPermission)
2936		err = client.Rename(testFileName, path.Join(vdirCryptPath, testFileName+".rename"))
2937		assert.ErrorIs(t, err, os.ErrPermission)
2938		err = client.Rename(testFileName, path.Join(vdirSFTPPath, testFileName+".rename"))
2939		assert.ErrorIs(t, err, os.ErrPermission)
2940		err = client.Rename(path.Join(vdirSFTPPath, testFileName), testFileName+".rename")
2941		assert.ErrorIs(t, err, os.ErrPermission)
2942		err = client.Rename(path.Join(vdirCryptPath, testFileName), testFileName+".rename")
2943		assert.ErrorIs(t, err, os.ErrPermission)
2944		// rename on local fs or on the same folder must work
2945		err = client.Rename(testFileName, testFileName+".rename")
2946		assert.NoError(t, err)
2947		err = client.Rename(path.Join(vdirSFTPPath, testFileName), path.Join(vdirSFTPPath, testFileName+"_rename"))
2948		assert.NoError(t, err)
2949		err = client.Rename(path.Join(vdirCryptPath, testFileName), path.Join(vdirCryptPath, testFileName+"_rename"))
2950		assert.NoError(t, err)
2951		// renaming a virtual folder is not allowed
2952		err = client.Rename(vdirSFTPPath, vdirSFTPPath+"_rename")
2953		assert.ErrorIs(t, err, os.ErrPermission)
2954		err = client.Rename(vdirCryptPath, vdirCryptPath+"_rename")
2955		assert.ErrorIs(t, err, os.ErrPermission)
2956		err = client.Rename(vdirCryptPath, path.Join(vdirCryptPath, "rename"))
2957		assert.ErrorIs(t, err, os.ErrPermission)
2958		err = client.Mkdir(path.Join(vdirCryptPath, "subcryptdir"))
2959		assert.NoError(t, err)
2960		err = client.Rename(path.Join(vdirCryptPath, "subcryptdir"), vdirCryptPath)
2961		assert.ErrorIs(t, err, os.ErrPermission)
2962		// renaming root folder is not allowed
2963		err = client.Rename("/", "new_name")
2964		assert.ErrorIs(t, err, os.ErrPermission)
2965		// renaming a path to a virtual folder is not allowed
2966		err = client.Rename("/vdir", "new_vdir")
2967		if assert.Error(t, err) {
2968			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
2969		}
2970	}
2971
2972	_, err = httpdtest.RemoveUser(user, http.StatusOK)
2973	assert.NoError(t, err)
2974	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderNameCrypt}, http.StatusOK)
2975	assert.NoError(t, err)
2976	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderNameSFTP}, http.StatusOK)
2977	assert.NoError(t, err)
2978	_, err = httpdtest.RemoveUser(baseUser, http.StatusOK)
2979	assert.NoError(t, err)
2980	err = os.RemoveAll(user.GetHomeDir())
2981	assert.NoError(t, err)
2982	err = os.RemoveAll(baseUser.GetHomeDir())
2983	assert.NoError(t, err)
2984	err = os.RemoveAll(mappedPathCrypt)
2985	assert.NoError(t, err)
2986	err = os.RemoveAll(mappedPathSFTP)
2987	assert.NoError(t, err)
2988}
2989
2990func TestNonLocalCrossRenameNonLocalBaseUser(t *testing.T) {
2991	baseUser, resp, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
2992	assert.NoError(t, err, string(resp))
2993	u := getTestSFTPUser()
2994	mappedPathLocal := filepath.Join(os.TempDir(), "local")
2995	folderNameLocal := filepath.Base(mappedPathLocal)
2996	vdirLocalPath := "/vdir/local"
2997	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
2998		BaseVirtualFolder: vfs.BaseVirtualFolder{
2999			Name:       folderNameLocal,
3000			MappedPath: mappedPathLocal,
3001		},
3002		VirtualPath: vdirLocalPath,
3003	})
3004	mappedPathCrypt := filepath.Join(os.TempDir(), "crypt")
3005	folderNameCrypt := filepath.Base(mappedPathCrypt)
3006	vdirCryptPath := "/vdir/crypt"
3007	u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
3008		BaseVirtualFolder: vfs.BaseVirtualFolder{
3009			Name: folderNameCrypt,
3010			FsConfig: vfs.Filesystem{
3011				Provider: sdk.CryptedFilesystemProvider,
3012				CryptConfig: vfs.CryptFsConfig{
3013					CryptFsConfig: sdk.CryptFsConfig{
3014						Passphrase: kms.NewPlainSecret(defaultPassword),
3015					},
3016				},
3017			},
3018			MappedPath: mappedPathCrypt,
3019		},
3020		VirtualPath: vdirCryptPath,
3021	})
3022	user, resp, err := httpdtest.AddUser(u, http.StatusCreated)
3023	assert.NoError(t, err, string(resp))
3024	conn, client, err := getSftpClient(user)
3025	if assert.NoError(t, err) {
3026		defer conn.Close()
3027		defer client.Close()
3028		assert.NoError(t, checkBasicSFTP(client))
3029		err = writeSFTPFile(testFileName, 4096, client)
3030		assert.NoError(t, err)
3031		err = writeSFTPFile(path.Join(vdirLocalPath, testFileName), 8192, client)
3032		assert.NoError(t, err)
3033		err = writeSFTPFile(path.Join(vdirCryptPath, testFileName), 16384, client)
3034		assert.NoError(t, err)
3035		err = client.Rename(path.Join(vdirLocalPath, testFileName), path.Join(vdirCryptPath, testFileName+".rename"))
3036		assert.ErrorIs(t, err, os.ErrPermission)
3037		err = client.Rename(path.Join(vdirCryptPath, testFileName), path.Join(vdirLocalPath, testFileName+".rename"))
3038		assert.ErrorIs(t, err, os.ErrPermission)
3039		err = client.Rename(testFileName, path.Join(vdirCryptPath, testFileName+".rename"))
3040		assert.ErrorIs(t, err, os.ErrPermission)
3041		err = client.Rename(testFileName, path.Join(vdirLocalPath, testFileName+".rename"))
3042		assert.ErrorIs(t, err, os.ErrPermission)
3043		err = client.Rename(path.Join(vdirLocalPath, testFileName), testFileName+".rename")
3044		assert.ErrorIs(t, err, os.ErrPermission)
3045		err = client.Rename(path.Join(vdirCryptPath, testFileName), testFileName+".rename")
3046		assert.ErrorIs(t, err, os.ErrPermission)
3047		// rename on local fs or on the same folder must work
3048		err = client.Rename(testFileName, testFileName+".rename")
3049		assert.NoError(t, err)
3050		err = client.Rename(path.Join(vdirLocalPath, testFileName), path.Join(vdirLocalPath, testFileName+"_rename"))
3051		assert.NoError(t, err)
3052		err = client.Rename(path.Join(vdirCryptPath, testFileName), path.Join(vdirCryptPath, testFileName+"_rename"))
3053		assert.NoError(t, err)
3054		// renaming a virtual folder is not allowed
3055		err = client.Rename(vdirLocalPath, vdirLocalPath+"_rename")
3056		assert.ErrorIs(t, err, os.ErrPermission)
3057		err = client.Rename(vdirCryptPath, vdirCryptPath+"_rename")
3058		assert.ErrorIs(t, err, os.ErrPermission)
3059		// renaming root folder is not allowed
3060		err = client.Rename("/", "new_name")
3061		assert.ErrorIs(t, err, os.ErrPermission)
3062		// renaming a path to a virtual folder is not allowed
3063		err = client.Rename("/vdir", "new_vdir")
3064		if assert.Error(t, err) {
3065			assert.Contains(t, err.Error(), "SSH_FX_OP_UNSUPPORTED")
3066		}
3067	}
3068
3069	_, err = httpdtest.RemoveUser(user, http.StatusOK)
3070	assert.NoError(t, err)
3071	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderNameCrypt}, http.StatusOK)
3072	assert.NoError(t, err)
3073	_, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderNameLocal}, http.StatusOK)
3074	assert.NoError(t, err)
3075	_, err = httpdtest.RemoveUser(baseUser, http.StatusOK)
3076	assert.NoError(t, err)
3077	err = os.RemoveAll(user.GetHomeDir())
3078	assert.NoError(t, err)
3079	err = os.RemoveAll(baseUser.GetHomeDir())
3080	assert.NoError(t, err)
3081	err = os.RemoveAll(mappedPathCrypt)
3082	assert.NoError(t, err)
3083	err = os.RemoveAll(mappedPathLocal)
3084	assert.NoError(t, err)
3085}
3086
3087func TestProxyProtocol(t *testing.T) {
3088	resp, err := httpclient.Get(fmt.Sprintf("http://%v", httpProxyAddr))
3089	if assert.NoError(t, err) {
3090		defer resp.Body.Close()
3091		assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
3092	}
3093}
3094
3095func TestSetProtocol(t *testing.T) {
3096	conn := common.NewBaseConnection("id", "sshd_exec", "", "", dataprovider.User{BaseUser: sdk.BaseUser{HomeDir: os.TempDir()}})
3097	conn.SetProtocol(common.ProtocolSCP)
3098	require.Equal(t, "SCP_id", conn.GetID())
3099}
3100
3101func TestGetFsError(t *testing.T) {
3102	u := getTestUser()
3103	u.FsConfig.Provider = sdk.GCSFilesystemProvider
3104	u.FsConfig.GCSConfig.Bucket = "test"
3105	u.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret("invalid JSON for credentials")
3106	conn := common.NewBaseConnection("", common.ProtocolFTP, "", "", u)
3107	_, _, err := conn.GetFsAndResolvedPath("/vpath")
3108	assert.Error(t, err)
3109}
3110
3111func waitTCPListening(address string) {
3112	for {
3113		conn, err := net.Dial("tcp", address)
3114		if err != nil {
3115			logger.WarnToConsole("tcp server %v not listening: %v", address, err)
3116			time.Sleep(100 * time.Millisecond)
3117			continue
3118		}
3119		logger.InfoToConsole("tcp server %v now listening", address)
3120		conn.Close()
3121		break
3122	}
3123}
3124
3125func checkBasicSFTP(client *sftp.Client) error {
3126	_, err := client.Getwd()
3127	if err != nil {
3128		return err
3129	}
3130	_, err = client.ReadDir(".")
3131	return err
3132}
3133
3134func getCustomAuthSftpClient(user dataprovider.User, authMethods []ssh.AuthMethod) (*ssh.Client, *sftp.Client, error) {
3135	var sftpClient *sftp.Client
3136	config := &ssh.ClientConfig{
3137		User: user.Username,
3138		HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
3139			return nil
3140		},
3141		Auth: authMethods,
3142	}
3143	conn, err := ssh.Dial("tcp", sftpServerAddr, config)
3144	if err != nil {
3145		return conn, sftpClient, err
3146	}
3147	sftpClient, err = sftp.NewClient(conn)
3148	if err != nil {
3149		conn.Close()
3150	}
3151	return conn, sftpClient, err
3152}
3153
3154func getSftpClient(user dataprovider.User) (*ssh.Client, *sftp.Client, error) {
3155	var sftpClient *sftp.Client
3156	config := &ssh.ClientConfig{
3157		User: user.Username,
3158		HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
3159			return nil
3160		},
3161	}
3162	if user.Password != "" {
3163		config.Auth = []ssh.AuthMethod{ssh.Password(user.Password)}
3164	} else {
3165		config.Auth = []ssh.AuthMethod{ssh.Password(defaultPassword)}
3166	}
3167
3168	conn, err := ssh.Dial("tcp", sftpServerAddr, config)
3169	if err != nil {
3170		return conn, sftpClient, err
3171	}
3172	sftpClient, err = sftp.NewClient(conn)
3173	if err != nil {
3174		conn.Close()
3175	}
3176	return conn, sftpClient, err
3177}
3178
3179func getTestUser() dataprovider.User {
3180	user := dataprovider.User{
3181		BaseUser: sdk.BaseUser{
3182			Username:       defaultUsername,
3183			Password:       defaultPassword,
3184			HomeDir:        filepath.Join(homeBasePath, defaultUsername),
3185			Status:         1,
3186			ExpirationDate: 0,
3187		},
3188	}
3189	user.Permissions = make(map[string][]string)
3190	user.Permissions["/"] = allPerms
3191	return user
3192}
3193
3194func getTestSFTPUser() dataprovider.User {
3195	u := getTestUser()
3196	u.Username = defaultSFTPUsername
3197	u.FsConfig.Provider = sdk.SFTPFilesystemProvider
3198	u.FsConfig.SFTPConfig.Endpoint = sftpServerAddr
3199	u.FsConfig.SFTPConfig.Username = defaultUsername
3200	u.FsConfig.SFTPConfig.Password = kms.NewPlainSecret(defaultPassword)
3201	return u
3202}
3203
3204func getCryptFsUser() dataprovider.User {
3205	u := getTestUser()
3206	u.FsConfig.Provider = sdk.CryptedFilesystemProvider
3207	u.FsConfig.CryptConfig.Passphrase = kms.NewPlainSecret(defaultPassword)
3208	return u
3209}
3210
3211func writeSFTPFile(name string, size int64, client *sftp.Client) error {
3212	err := writeSFTPFileNoCheck(name, size, client)
3213	if err != nil {
3214		return err
3215	}
3216	info, err := client.Stat(name)
3217	if err != nil {
3218		return err
3219	}
3220	if info.Size() != size {
3221		return fmt.Errorf("file size mismatch, wanted %v, actual %v", size, info.Size())
3222	}
3223	return nil
3224}
3225
3226func writeSFTPFileNoCheck(name string, size int64, client *sftp.Client) error {
3227	content := make([]byte, size)
3228	_, err := rand.Read(content)
3229	if err != nil {
3230		return err
3231	}
3232	f, err := client.Create(name)
3233	if err != nil {
3234		return err
3235	}
3236	_, err = io.Copy(f, bytes.NewBuffer(content))
3237	if err != nil {
3238		f.Close()
3239		return err
3240	}
3241	return f.Close()
3242}
3243
3244func getUploadScriptContent(movedPath string) []byte {
3245	content := []byte("#!/bin/sh\n\n")
3246	content = append(content, []byte("sleep 1\n")...)
3247	content = append(content, []byte(fmt.Sprintf("mv ${SFTPGO_ACTION_PATH} %v\n", movedPath))...)
3248	return content
3249}
3250
3251func generateTOTPPasscode(secret string, algo otp.Algorithm) (string, error) {
3252	return totp.GenerateCodeCustom(secret, time.Now(), totp.ValidateOpts{
3253		Period:    30,
3254		Skew:      1,
3255		Digits:    otp.DigitsSix,
3256		Algorithm: algo,
3257	})
3258}
3259