connection_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. package common
  2. import (
  3. "os"
  4. "path"
  5. "path/filepath"
  6. "runtime"
  7. "testing"
  8. "time"
  9. "github.com/pkg/sftp"
  10. "github.com/rs/xid"
  11. "github.com/sftpgo/sdk"
  12. "github.com/stretchr/testify/assert"
  13. "github.com/drakkan/sftpgo/v2/dataprovider"
  14. "github.com/drakkan/sftpgo/v2/kms"
  15. "github.com/drakkan/sftpgo/v2/vfs"
  16. )
  17. // MockOsFs mockable OsFs
  18. type MockOsFs struct {
  19. vfs.Fs
  20. hasVirtualFolders bool
  21. }
  22. // Name returns the name for the Fs implementation
  23. func (fs *MockOsFs) Name() string {
  24. return "mockOsFs"
  25. }
  26. // HasVirtualFolders returns true if folders are emulated
  27. func (fs *MockOsFs) HasVirtualFolders() bool {
  28. return fs.hasVirtualFolders
  29. }
  30. func (fs *MockOsFs) IsUploadResumeSupported() bool {
  31. return !fs.hasVirtualFolders
  32. }
  33. func (fs *MockOsFs) Chtimes(name string, atime, mtime time.Time, isUploading bool) error {
  34. return vfs.ErrVfsUnsupported
  35. }
  36. func newMockOsFs(hasVirtualFolders bool, connectionID, rootDir string) vfs.Fs {
  37. return &MockOsFs{
  38. Fs: vfs.NewOsFs(connectionID, rootDir, ""),
  39. hasVirtualFolders: hasVirtualFolders,
  40. }
  41. }
  42. func TestRemoveErrors(t *testing.T) {
  43. mappedPath := filepath.Join(os.TempDir(), "map")
  44. homePath := filepath.Join(os.TempDir(), "home")
  45. user := dataprovider.User{
  46. BaseUser: sdk.BaseUser{
  47. Username: "remove_errors_user",
  48. HomeDir: homePath,
  49. },
  50. VirtualFolders: []vfs.VirtualFolder{
  51. {
  52. BaseVirtualFolder: vfs.BaseVirtualFolder{
  53. Name: filepath.Base(mappedPath),
  54. MappedPath: mappedPath,
  55. },
  56. VirtualPath: "/virtualpath",
  57. },
  58. },
  59. }
  60. user.Permissions = make(map[string][]string)
  61. user.Permissions["/"] = []string{dataprovider.PermAny}
  62. fs := vfs.NewOsFs("", os.TempDir(), "")
  63. conn := NewBaseConnection("", ProtocolFTP, "", "", user)
  64. err := conn.IsRemoveDirAllowed(fs, mappedPath, "/virtualpath1")
  65. if assert.Error(t, err) {
  66. assert.Contains(t, err.Error(), "permission denied")
  67. }
  68. err = conn.RemoveFile(fs, filepath.Join(homePath, "missing_file"), "/missing_file",
  69. vfs.NewFileInfo("info", false, 100, time.Now(), false))
  70. assert.Error(t, err)
  71. }
  72. func TestSetStatMode(t *testing.T) {
  73. oldSetStatMode := Config.SetstatMode
  74. Config.SetstatMode = 1
  75. fakePath := "fake path"
  76. user := dataprovider.User{
  77. BaseUser: sdk.BaseUser{
  78. HomeDir: os.TempDir(),
  79. },
  80. }
  81. user.Permissions = make(map[string][]string)
  82. user.Permissions["/"] = []string{dataprovider.PermAny}
  83. fs := newMockOsFs(true, "", user.GetHomeDir())
  84. conn := NewBaseConnection("", ProtocolWebDAV, "", "", user)
  85. err := conn.handleChmod(fs, fakePath, fakePath, nil)
  86. assert.NoError(t, err)
  87. err = conn.handleChown(fs, fakePath, fakePath, nil)
  88. assert.NoError(t, err)
  89. err = conn.handleChtimes(fs, fakePath, fakePath, nil)
  90. assert.NoError(t, err)
  91. Config.SetstatMode = 2
  92. err = conn.handleChmod(fs, fakePath, fakePath, nil)
  93. assert.NoError(t, err)
  94. err = conn.handleChtimes(fs, fakePath, fakePath, &StatAttributes{
  95. Atime: time.Now(),
  96. Mtime: time.Now(),
  97. })
  98. assert.NoError(t, err)
  99. Config.SetstatMode = oldSetStatMode
  100. }
  101. func TestRecursiveRenameWalkError(t *testing.T) {
  102. fs := vfs.NewOsFs("", os.TempDir(), "")
  103. conn := NewBaseConnection("", ProtocolWebDAV, "", "", dataprovider.User{})
  104. err := conn.checkRecursiveRenameDirPermissions(fs, fs, "/source", "/target")
  105. assert.ErrorIs(t, err, os.ErrNotExist)
  106. }
  107. func TestCrossRenameFsErrors(t *testing.T) {
  108. fs := vfs.NewOsFs("", os.TempDir(), "")
  109. conn := NewBaseConnection("", ProtocolWebDAV, "", "", dataprovider.User{})
  110. res := conn.hasSpaceForCrossRename(fs, vfs.QuotaCheckResult{}, 1, "missingsource")
  111. assert.False(t, res)
  112. if runtime.GOOS != osWindows {
  113. dirPath := filepath.Join(os.TempDir(), "d")
  114. err := os.Mkdir(dirPath, os.ModePerm)
  115. assert.NoError(t, err)
  116. err = os.Chmod(dirPath, 0001)
  117. assert.NoError(t, err)
  118. res = conn.hasSpaceForCrossRename(fs, vfs.QuotaCheckResult{}, 1, dirPath)
  119. assert.False(t, res)
  120. err = os.Chmod(dirPath, os.ModePerm)
  121. assert.NoError(t, err)
  122. err = os.Remove(dirPath)
  123. assert.NoError(t, err)
  124. }
  125. }
  126. func TestRenameVirtualFolders(t *testing.T) {
  127. vdir := "/avdir"
  128. u := dataprovider.User{}
  129. u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
  130. BaseVirtualFolder: vfs.BaseVirtualFolder{
  131. Name: "name",
  132. MappedPath: "mappedPath",
  133. },
  134. VirtualPath: vdir,
  135. })
  136. fs := vfs.NewOsFs("", os.TempDir(), "")
  137. conn := NewBaseConnection("", ProtocolFTP, "", "", u)
  138. res := conn.isRenamePermitted(fs, fs, "source", "target", vdir, "vdirtarget", nil)
  139. assert.False(t, res)
  140. }
  141. func TestRenamePerms(t *testing.T) {
  142. src := "source"
  143. target := "target"
  144. u := dataprovider.User{}
  145. u.Permissions = map[string][]string{}
  146. u.Permissions["/"] = []string{dataprovider.PermCreateDirs, dataprovider.PermUpload, dataprovider.PermCreateSymlinks,
  147. dataprovider.PermDeleteFiles}
  148. conn := NewBaseConnection("", ProtocolSFTP, "", "", u)
  149. assert.False(t, conn.hasRenamePerms(src, target, nil))
  150. u.Permissions["/"] = []string{dataprovider.PermCreateDirs, dataprovider.PermUpload, dataprovider.PermCreateSymlinks,
  151. dataprovider.PermDeleteFiles, dataprovider.PermDeleteDirs}
  152. assert.True(t, conn.hasRenamePerms(src, target, nil))
  153. u.Permissions["/"] = []string{dataprovider.PermCreateDirs, dataprovider.PermUpload, dataprovider.PermDeleteFiles,
  154. dataprovider.PermDeleteDirs}
  155. assert.False(t, conn.hasRenamePerms(src, target, nil))
  156. info := vfs.NewFileInfo(src, true, 0, time.Now(), false)
  157. u.Permissions["/"] = []string{dataprovider.PermCreateDirs, dataprovider.PermUpload, dataprovider.PermDeleteFiles}
  158. assert.False(t, conn.hasRenamePerms(src, target, info))
  159. u.Permissions["/"] = []string{dataprovider.PermCreateDirs, dataprovider.PermUpload, dataprovider.PermDeleteDirs}
  160. assert.True(t, conn.hasRenamePerms(src, target, info))
  161. u.Permissions["/"] = []string{dataprovider.PermDownload, dataprovider.PermUpload, dataprovider.PermDeleteDirs}
  162. assert.False(t, conn.hasRenamePerms(src, target, info))
  163. }
  164. func TestUpdateQuotaAfterRename(t *testing.T) {
  165. user := dataprovider.User{
  166. BaseUser: sdk.BaseUser{
  167. Username: userTestUsername,
  168. HomeDir: filepath.Join(os.TempDir(), "home"),
  169. },
  170. }
  171. mappedPath := filepath.Join(os.TempDir(), "vdir")
  172. user.Permissions = make(map[string][]string)
  173. user.Permissions["/"] = []string{dataprovider.PermAny}
  174. user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{
  175. BaseVirtualFolder: vfs.BaseVirtualFolder{
  176. MappedPath: mappedPath,
  177. },
  178. VirtualPath: "/vdir",
  179. QuotaFiles: -1,
  180. QuotaSize: -1,
  181. })
  182. user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{
  183. BaseVirtualFolder: vfs.BaseVirtualFolder{
  184. MappedPath: mappedPath,
  185. },
  186. VirtualPath: "/vdir1",
  187. QuotaFiles: -1,
  188. QuotaSize: -1,
  189. })
  190. err := os.MkdirAll(user.GetHomeDir(), os.ModePerm)
  191. assert.NoError(t, err)
  192. err = os.MkdirAll(mappedPath, os.ModePerm)
  193. assert.NoError(t, err)
  194. fs, err := user.GetFilesystem("id")
  195. assert.NoError(t, err)
  196. c := NewBaseConnection("", ProtocolSFTP, "", "", user)
  197. request := sftp.NewRequest("Rename", "/testfile")
  198. if runtime.GOOS != osWindows {
  199. request.Filepath = "/dir"
  200. request.Target = path.Join("/vdir", "dir")
  201. testDirPath := filepath.Join(mappedPath, "dir")
  202. err := os.MkdirAll(testDirPath, os.ModePerm)
  203. assert.NoError(t, err)
  204. err = os.Chmod(testDirPath, 0001)
  205. assert.NoError(t, err)
  206. err = c.updateQuotaAfterRename(fs, request.Filepath, request.Target, testDirPath, 0)
  207. assert.Error(t, err)
  208. err = os.Chmod(testDirPath, os.ModePerm)
  209. assert.NoError(t, err)
  210. }
  211. testFile1 := "/testfile1"
  212. request.Target = testFile1
  213. request.Filepath = path.Join("/vdir", "file")
  214. err = c.updateQuotaAfterRename(fs, request.Filepath, request.Target, filepath.Join(mappedPath, "file"), 0)
  215. assert.Error(t, err)
  216. err = os.WriteFile(filepath.Join(mappedPath, "file"), []byte("test content"), os.ModePerm)
  217. assert.NoError(t, err)
  218. request.Filepath = testFile1
  219. request.Target = path.Join("/vdir", "file")
  220. err = c.updateQuotaAfterRename(fs, request.Filepath, request.Target, filepath.Join(mappedPath, "file"), 12)
  221. assert.NoError(t, err)
  222. err = os.WriteFile(filepath.Join(user.GetHomeDir(), "testfile1"), []byte("test content"), os.ModePerm)
  223. assert.NoError(t, err)
  224. request.Target = testFile1
  225. request.Filepath = path.Join("/vdir", "file")
  226. err = c.updateQuotaAfterRename(fs, request.Filepath, request.Target, filepath.Join(mappedPath, "file"), 12)
  227. assert.NoError(t, err)
  228. request.Target = path.Join("/vdir1", "file")
  229. request.Filepath = path.Join("/vdir", "file")
  230. err = c.updateQuotaAfterRename(fs, request.Filepath, request.Target, filepath.Join(mappedPath, "file"), 12)
  231. assert.NoError(t, err)
  232. err = os.RemoveAll(mappedPath)
  233. assert.NoError(t, err)
  234. err = os.RemoveAll(user.GetHomeDir())
  235. assert.NoError(t, err)
  236. }
  237. func TestErrorsMapping(t *testing.T) {
  238. fs := vfs.NewOsFs("", os.TempDir(), "")
  239. conn := NewBaseConnection("", ProtocolSFTP, "", "", dataprovider.User{BaseUser: sdk.BaseUser{HomeDir: os.TempDir()}})
  240. for _, protocol := range supportedProtocols {
  241. conn.SetProtocol(protocol)
  242. err := conn.GetFsError(fs, os.ErrNotExist)
  243. if protocol == ProtocolSFTP {
  244. assert.ErrorIs(t, err, sftp.ErrSSHFxNoSuchFile)
  245. } else if protocol == ProtocolWebDAV || protocol == ProtocolFTP || protocol == ProtocolHTTP ||
  246. protocol == ProtocolHTTPShare || protocol == ProtocolDataRetention {
  247. assert.EqualError(t, err, os.ErrNotExist.Error())
  248. } else {
  249. assert.EqualError(t, err, ErrNotExist.Error())
  250. }
  251. err = conn.GetFsError(fs, os.ErrPermission)
  252. if protocol == ProtocolSFTP {
  253. assert.EqualError(t, err, sftp.ErrSSHFxPermissionDenied.Error())
  254. } else {
  255. assert.EqualError(t, err, ErrPermissionDenied.Error())
  256. }
  257. err = conn.GetFsError(fs, os.ErrClosed)
  258. if protocol == ProtocolSFTP {
  259. assert.ErrorIs(t, err, sftp.ErrSSHFxFailure)
  260. assert.Contains(t, err.Error(), os.ErrClosed.Error())
  261. } else {
  262. assert.EqualError(t, err, ErrGenericFailure.Error())
  263. }
  264. err = conn.GetFsError(fs, ErrPermissionDenied)
  265. if protocol == ProtocolSFTP {
  266. assert.ErrorIs(t, err, sftp.ErrSSHFxFailure)
  267. assert.Contains(t, err.Error(), ErrPermissionDenied.Error())
  268. } else {
  269. assert.EqualError(t, err, ErrPermissionDenied.Error())
  270. }
  271. err = conn.GetFsError(fs, vfs.ErrVfsUnsupported)
  272. if protocol == ProtocolSFTP {
  273. assert.EqualError(t, err, sftp.ErrSSHFxOpUnsupported.Error())
  274. } else {
  275. assert.EqualError(t, err, ErrOpUnsupported.Error())
  276. }
  277. err = conn.GetFsError(fs, vfs.ErrStorageSizeUnavailable)
  278. if protocol == ProtocolSFTP {
  279. assert.ErrorIs(t, err, sftp.ErrSSHFxOpUnsupported)
  280. assert.Contains(t, err.Error(), vfs.ErrStorageSizeUnavailable.Error())
  281. } else {
  282. assert.EqualError(t, err, vfs.ErrStorageSizeUnavailable.Error())
  283. }
  284. err = conn.GetQuotaExceededError()
  285. assert.True(t, conn.IsQuotaExceededError(err))
  286. err = conn.GetReadQuotaExceededError()
  287. if protocol == ProtocolSFTP {
  288. assert.ErrorIs(t, err, sftp.ErrSSHFxFailure)
  289. assert.Contains(t, err.Error(), ErrReadQuotaExceeded.Error())
  290. } else {
  291. assert.ErrorIs(t, err, ErrReadQuotaExceeded)
  292. }
  293. err = conn.GetNotExistError()
  294. assert.True(t, conn.IsNotExistError(err))
  295. err = conn.GetFsError(fs, nil)
  296. assert.NoError(t, err)
  297. err = conn.GetOpUnsupportedError()
  298. if protocol == ProtocolSFTP {
  299. assert.EqualError(t, err, sftp.ErrSSHFxOpUnsupported.Error())
  300. } else {
  301. assert.EqualError(t, err, ErrOpUnsupported.Error())
  302. }
  303. }
  304. }
  305. func TestMaxWriteSize(t *testing.T) {
  306. permissions := make(map[string][]string)
  307. permissions["/"] = []string{dataprovider.PermAny}
  308. user := dataprovider.User{
  309. BaseUser: sdk.BaseUser{
  310. Username: userTestUsername,
  311. Permissions: permissions,
  312. HomeDir: filepath.Clean(os.TempDir()),
  313. },
  314. }
  315. fs, err := user.GetFilesystem("123")
  316. assert.NoError(t, err)
  317. conn := NewBaseConnection("", ProtocolFTP, "", "", user)
  318. quotaResult := vfs.QuotaCheckResult{
  319. HasSpace: true,
  320. }
  321. size, err := conn.GetMaxWriteSize(quotaResult, false, 0, fs.IsUploadResumeSupported())
  322. assert.NoError(t, err)
  323. assert.Equal(t, int64(0), size)
  324. conn.User.Filters.MaxUploadFileSize = 100
  325. size, err = conn.GetMaxWriteSize(quotaResult, false, 0, fs.IsUploadResumeSupported())
  326. assert.NoError(t, err)
  327. assert.Equal(t, int64(100), size)
  328. quotaResult.QuotaSize = 1000
  329. size, err = conn.GetMaxWriteSize(quotaResult, false, 50, fs.IsUploadResumeSupported())
  330. assert.NoError(t, err)
  331. assert.Equal(t, int64(100), size)
  332. quotaResult.QuotaSize = 1000
  333. quotaResult.UsedSize = 990
  334. size, err = conn.GetMaxWriteSize(quotaResult, false, 50, fs.IsUploadResumeSupported())
  335. assert.NoError(t, err)
  336. assert.Equal(t, int64(60), size)
  337. quotaResult.QuotaSize = 0
  338. quotaResult.UsedSize = 0
  339. size, err = conn.GetMaxWriteSize(quotaResult, true, 100, fs.IsUploadResumeSupported())
  340. assert.True(t, conn.IsQuotaExceededError(err))
  341. assert.Equal(t, int64(0), size)
  342. size, err = conn.GetMaxWriteSize(quotaResult, true, 10, fs.IsUploadResumeSupported())
  343. assert.NoError(t, err)
  344. assert.Equal(t, int64(90), size)
  345. fs = newMockOsFs(true, fs.ConnectionID(), user.GetHomeDir())
  346. size, err = conn.GetMaxWriteSize(quotaResult, true, 100, fs.IsUploadResumeSupported())
  347. assert.EqualError(t, err, ErrOpUnsupported.Error())
  348. assert.Equal(t, int64(0), size)
  349. }
  350. func TestCheckParentDirsErrors(t *testing.T) {
  351. permissions := make(map[string][]string)
  352. permissions["/"] = []string{dataprovider.PermAny}
  353. user := dataprovider.User{
  354. BaseUser: sdk.BaseUser{
  355. Username: userTestUsername,
  356. Permissions: permissions,
  357. HomeDir: filepath.Clean(os.TempDir()),
  358. },
  359. FsConfig: vfs.Filesystem{
  360. Provider: sdk.CryptedFilesystemProvider,
  361. },
  362. }
  363. c := NewBaseConnection(xid.New().String(), ProtocolSFTP, "", "", user)
  364. err := c.CheckParentDirs("/a/dir")
  365. assert.Error(t, err)
  366. user.FsConfig.Provider = sdk.LocalFilesystemProvider
  367. user.VirtualFolders = nil
  368. user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{
  369. BaseVirtualFolder: vfs.BaseVirtualFolder{
  370. FsConfig: vfs.Filesystem{
  371. Provider: sdk.CryptedFilesystemProvider,
  372. },
  373. },
  374. VirtualPath: "/vdir",
  375. })
  376. user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{
  377. BaseVirtualFolder: vfs.BaseVirtualFolder{
  378. MappedPath: filepath.Clean(os.TempDir()),
  379. },
  380. VirtualPath: "/vdir/sub",
  381. })
  382. c = NewBaseConnection(xid.New().String(), ProtocolSFTP, "", "", user)
  383. err = c.CheckParentDirs("/vdir/sub/dir")
  384. assert.Error(t, err)
  385. user = dataprovider.User{
  386. BaseUser: sdk.BaseUser{
  387. Username: userTestUsername,
  388. Permissions: permissions,
  389. HomeDir: filepath.Clean(os.TempDir()),
  390. },
  391. FsConfig: vfs.Filesystem{
  392. Provider: sdk.S3FilesystemProvider,
  393. S3Config: vfs.S3FsConfig{
  394. BaseS3FsConfig: sdk.BaseS3FsConfig{
  395. Bucket: "buck",
  396. Region: "us-east-1",
  397. AccessKey: "key",
  398. },
  399. AccessSecret: kms.NewPlainSecret("s3secret"),
  400. },
  401. },
  402. }
  403. c = NewBaseConnection(xid.New().String(), ProtocolSFTP, "", "", user)
  404. err = c.CheckParentDirs("/a/dir")
  405. assert.NoError(t, err)
  406. user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{
  407. BaseVirtualFolder: vfs.BaseVirtualFolder{
  408. MappedPath: filepath.Clean(os.TempDir()),
  409. },
  410. VirtualPath: "/local/dir",
  411. })
  412. c = NewBaseConnection(xid.New().String(), ProtocolSFTP, "", "", user)
  413. err = c.CheckParentDirs("/local/dir/sub-dir")
  414. assert.NoError(t, err)
  415. err = os.RemoveAll(filepath.Join(os.TempDir(), "sub-dir"))
  416. assert.NoError(t, err)
  417. }