connection_test.go 16 KB

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