internal_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. package httpd
  2. import (
  3. "context"
  4. "fmt"
  5. "html/template"
  6. "io/ioutil"
  7. "net/http"
  8. "net/http/httptest"
  9. "net/url"
  10. "os"
  11. "path/filepath"
  12. "runtime"
  13. "strings"
  14. "testing"
  15. "github.com/drakkan/sftpgo/dataprovider"
  16. "github.com/drakkan/sftpgo/sftpd"
  17. "github.com/drakkan/sftpgo/utils"
  18. "github.com/drakkan/sftpgo/vfs"
  19. "github.com/go-chi/chi"
  20. "github.com/stretchr/testify/assert"
  21. )
  22. const (
  23. invalidURL = "http://foo\x7f.com/"
  24. inactiveURL = "http://127.0.0.1:12345"
  25. )
  26. func TestGetRespStatus(t *testing.T) {
  27. var err error
  28. err = &dataprovider.MethodDisabledError{}
  29. respStatus := getRespStatus(err)
  30. assert.Equal(t, http.StatusForbidden, respStatus)
  31. err = fmt.Errorf("generic error")
  32. respStatus = getRespStatus(err)
  33. assert.Equal(t, http.StatusInternalServerError, respStatus)
  34. }
  35. func TestCheckResponse(t *testing.T) {
  36. err := checkResponse(http.StatusOK, http.StatusCreated)
  37. assert.Error(t, err)
  38. err = checkResponse(http.StatusBadRequest, http.StatusBadRequest)
  39. assert.NoError(t, err)
  40. }
  41. func TestCheckUser(t *testing.T) {
  42. expected := &dataprovider.User{}
  43. actual := &dataprovider.User{}
  44. actual.Password = "password"
  45. err := checkUser(expected, actual)
  46. assert.Error(t, err)
  47. actual.Password = ""
  48. err = checkUser(expected, actual)
  49. assert.Error(t, err)
  50. expected.ID = 1
  51. actual.ID = 2
  52. err = checkUser(expected, actual)
  53. assert.Error(t, err)
  54. expected.ID = 2
  55. actual.ID = 2
  56. expected.Permissions = make(map[string][]string)
  57. expected.Permissions["/"] = []string{dataprovider.PermCreateDirs, dataprovider.PermDelete, dataprovider.PermDownload}
  58. actual.Permissions = make(map[string][]string)
  59. err = checkUser(expected, actual)
  60. assert.Error(t, err)
  61. actual.Permissions["/"] = []string{dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks}
  62. err = checkUser(expected, actual)
  63. assert.Error(t, err)
  64. expected.Permissions["/"] = append(expected.Permissions["/"], dataprovider.PermRename)
  65. err = checkUser(expected, actual)
  66. assert.Error(t, err)
  67. expected.Permissions = make(map[string][]string)
  68. expected.Permissions["/somedir"] = []string{dataprovider.PermAny}
  69. actual.Permissions = make(map[string][]string)
  70. actual.Permissions["/otherdir"] = []string{dataprovider.PermCreateDirs, dataprovider.PermCreateSymlinks}
  71. err = checkUser(expected, actual)
  72. assert.Error(t, err)
  73. expected.Permissions = make(map[string][]string)
  74. actual.Permissions = make(map[string][]string)
  75. actual.FsConfig.Provider = 1
  76. err = checkUser(expected, actual)
  77. assert.Error(t, err)
  78. actual.FsConfig.Provider = 0
  79. expected.VirtualFolders = append(expected.VirtualFolders, vfs.VirtualFolder{
  80. VirtualPath: "/vdir",
  81. MappedPath: os.TempDir(),
  82. })
  83. err = checkUser(expected, actual)
  84. assert.Error(t, err)
  85. actual.VirtualFolders = append(actual.VirtualFolders, vfs.VirtualFolder{
  86. VirtualPath: "/vdir1",
  87. MappedPath: os.TempDir(),
  88. })
  89. err = checkUser(expected, actual)
  90. assert.Error(t, err)
  91. }
  92. func TestCompareUserFilters(t *testing.T) {
  93. expected := &dataprovider.User{}
  94. actual := &dataprovider.User{}
  95. actual.ID = 1
  96. expected.ID = 1
  97. expected.Filters.AllowedIP = []string{}
  98. actual.Filters.AllowedIP = []string{"192.168.1.2/32"}
  99. err := checkUser(expected, actual)
  100. assert.Error(t, err)
  101. expected.Filters.AllowedIP = []string{"192.168.1.3/32"}
  102. err = checkUser(expected, actual)
  103. assert.Error(t, err)
  104. expected.Filters.AllowedIP = []string{}
  105. actual.Filters.AllowedIP = []string{}
  106. expected.Filters.DeniedIP = []string{}
  107. actual.Filters.DeniedIP = []string{"192.168.1.2/32"}
  108. err = checkUser(expected, actual)
  109. assert.Error(t, err)
  110. expected.Filters.DeniedIP = []string{"192.168.1.3/32"}
  111. err = checkUser(expected, actual)
  112. assert.Error(t, err)
  113. expected.Filters.DeniedIP = []string{}
  114. actual.Filters.DeniedIP = []string{}
  115. expected.Filters.DeniedLoginMethods = []string{}
  116. actual.Filters.DeniedLoginMethods = []string{dataprovider.SSHLoginMethodPublicKey}
  117. err = checkUser(expected, actual)
  118. assert.Error(t, err)
  119. expected.Filters.DeniedLoginMethods = []string{dataprovider.SSHLoginMethodPassword}
  120. err = checkUser(expected, actual)
  121. assert.Error(t, err)
  122. expected.Filters.DeniedLoginMethods = []string{}
  123. actual.Filters.DeniedLoginMethods = []string{}
  124. expected.Filters.FileExtensions = append(expected.Filters.FileExtensions, dataprovider.ExtensionsFilter{
  125. Path: "/",
  126. AllowedExtensions: []string{".jpg", ".png"},
  127. DeniedExtensions: []string{".zip", ".rar"},
  128. })
  129. err = checkUser(expected, actual)
  130. assert.Error(t, err)
  131. actual.Filters.FileExtensions = append(actual.Filters.FileExtensions, dataprovider.ExtensionsFilter{
  132. Path: "/sub",
  133. AllowedExtensions: []string{".jpg", ".png"},
  134. DeniedExtensions: []string{".zip", ".rar"},
  135. })
  136. err = checkUser(expected, actual)
  137. assert.Error(t, err)
  138. actual.Filters.FileExtensions[0] = dataprovider.ExtensionsFilter{
  139. Path: "/",
  140. AllowedExtensions: []string{".jpg"},
  141. DeniedExtensions: []string{".zip", ".rar"},
  142. }
  143. err = checkUser(expected, actual)
  144. assert.Error(t, err)
  145. actual.Filters.FileExtensions[0] = dataprovider.ExtensionsFilter{
  146. Path: "/",
  147. AllowedExtensions: []string{".tiff", ".png"},
  148. DeniedExtensions: []string{".zip", ".rar"},
  149. }
  150. err = checkUser(expected, actual)
  151. assert.Error(t, err)
  152. actual.Filters.FileExtensions[0] = dataprovider.ExtensionsFilter{
  153. Path: "/",
  154. AllowedExtensions: []string{".jpg", ".png"},
  155. DeniedExtensions: []string{".tar.gz", ".rar"},
  156. }
  157. err = checkUser(expected, actual)
  158. assert.Error(t, err)
  159. }
  160. func TestCompareUserFields(t *testing.T) {
  161. expected := &dataprovider.User{}
  162. actual := &dataprovider.User{}
  163. expected.Permissions = make(map[string][]string)
  164. actual.Permissions = make(map[string][]string)
  165. expected.Username = "test"
  166. err := compareEqualsUserFields(expected, actual)
  167. assert.Error(t, err)
  168. expected.Username = ""
  169. expected.HomeDir = "homedir"
  170. err = compareEqualsUserFields(expected, actual)
  171. assert.Error(t, err)
  172. expected.HomeDir = ""
  173. expected.UID = 1
  174. err = compareEqualsUserFields(expected, actual)
  175. assert.Error(t, err)
  176. expected.UID = 0
  177. expected.GID = 1
  178. err = compareEqualsUserFields(expected, actual)
  179. assert.Error(t, err)
  180. expected.GID = 0
  181. expected.MaxSessions = 2
  182. err = compareEqualsUserFields(expected, actual)
  183. assert.Error(t, err)
  184. expected.MaxSessions = 0
  185. expected.QuotaSize = 4096
  186. err = compareEqualsUserFields(expected, actual)
  187. assert.Error(t, err)
  188. expected.QuotaSize = 0
  189. expected.QuotaFiles = 2
  190. err = compareEqualsUserFields(expected, actual)
  191. assert.Error(t, err)
  192. expected.QuotaFiles = 0
  193. expected.Permissions["/"] = []string{dataprovider.PermCreateDirs}
  194. err = compareEqualsUserFields(expected, actual)
  195. assert.Error(t, err)
  196. expected.Permissions = nil
  197. expected.UploadBandwidth = 64
  198. err = compareEqualsUserFields(expected, actual)
  199. assert.Error(t, err)
  200. expected.UploadBandwidth = 0
  201. expected.DownloadBandwidth = 128
  202. err = compareEqualsUserFields(expected, actual)
  203. assert.Error(t, err)
  204. expected.DownloadBandwidth = 0
  205. expected.Status = 1
  206. err = compareEqualsUserFields(expected, actual)
  207. assert.Error(t, err)
  208. expected.Status = 0
  209. expected.ExpirationDate = 123
  210. err = compareEqualsUserFields(expected, actual)
  211. assert.Error(t, err)
  212. }
  213. func TestCompareUserFsConfig(t *testing.T) {
  214. expected := &dataprovider.User{}
  215. actual := &dataprovider.User{}
  216. expected.FsConfig.Provider = 1
  217. err := compareUserFsConfig(expected, actual)
  218. assert.Error(t, err)
  219. expected.FsConfig.Provider = 0
  220. expected.FsConfig.S3Config.Bucket = "bucket"
  221. err = compareUserFsConfig(expected, actual)
  222. assert.Error(t, err)
  223. expected.FsConfig.S3Config.Bucket = ""
  224. expected.FsConfig.S3Config.Region = "region"
  225. err = compareUserFsConfig(expected, actual)
  226. assert.Error(t, err)
  227. expected.FsConfig.S3Config.Region = ""
  228. expected.FsConfig.S3Config.AccessKey = "access key"
  229. err = compareUserFsConfig(expected, actual)
  230. assert.Error(t, err)
  231. expected.FsConfig.S3Config.AccessKey = ""
  232. actual.FsConfig.S3Config.AccessSecret = "access secret"
  233. err = compareUserFsConfig(expected, actual)
  234. assert.Error(t, err)
  235. secret, _ := utils.EncryptData("access secret")
  236. actual.FsConfig.S3Config.AccessSecret = ""
  237. expected.FsConfig.S3Config.AccessSecret = secret
  238. err = compareUserFsConfig(expected, actual)
  239. assert.Error(t, err)
  240. expected.FsConfig.S3Config.AccessSecret = utils.RemoveDecryptionKey(secret)
  241. actual.FsConfig.S3Config.AccessSecret = utils.RemoveDecryptionKey(secret) + "a"
  242. err = compareUserFsConfig(expected, actual)
  243. assert.Error(t, err)
  244. expected.FsConfig.S3Config.AccessSecret = "test"
  245. actual.FsConfig.S3Config.AccessSecret = ""
  246. err = compareUserFsConfig(expected, actual)
  247. assert.Error(t, err)
  248. expected.FsConfig.S3Config.AccessSecret = ""
  249. actual.FsConfig.S3Config.AccessSecret = ""
  250. expected.FsConfig.S3Config.Endpoint = "http://127.0.0.1:9000/"
  251. err = compareUserFsConfig(expected, actual)
  252. assert.Error(t, err)
  253. expected.FsConfig.S3Config.Endpoint = ""
  254. expected.FsConfig.S3Config.StorageClass = "Standard"
  255. err = compareUserFsConfig(expected, actual)
  256. assert.Error(t, err)
  257. expected.FsConfig.S3Config.StorageClass = ""
  258. expected.FsConfig.S3Config.KeyPrefix = "somedir/subdir"
  259. err = compareUserFsConfig(expected, actual)
  260. assert.Error(t, err)
  261. expected.FsConfig.S3Config.KeyPrefix = ""
  262. expected.FsConfig.S3Config.UploadPartSize = 10
  263. err = compareUserFsConfig(expected, actual)
  264. assert.Error(t, err)
  265. expected.FsConfig.S3Config.UploadPartSize = 0
  266. expected.FsConfig.S3Config.UploadConcurrency = 3
  267. err = compareUserFsConfig(expected, actual)
  268. assert.Error(t, err)
  269. }
  270. func TestCompareUserGCSConfig(t *testing.T) {
  271. expected := &dataprovider.User{}
  272. actual := &dataprovider.User{}
  273. expected.FsConfig.GCSConfig.KeyPrefix = "somedir/subdir"
  274. err := compareUserFsConfig(expected, actual)
  275. assert.Error(t, err)
  276. expected.FsConfig.GCSConfig.KeyPrefix = ""
  277. expected.FsConfig.GCSConfig.Bucket = "bucket"
  278. err = compareUserFsConfig(expected, actual)
  279. assert.Error(t, err)
  280. expected.FsConfig.GCSConfig.Bucket = ""
  281. expected.FsConfig.GCSConfig.StorageClass = "Standard"
  282. err = compareUserFsConfig(expected, actual)
  283. assert.Error(t, err)
  284. expected.FsConfig.GCSConfig.StorageClass = ""
  285. expected.FsConfig.GCSConfig.AutomaticCredentials = 1
  286. err = compareUserFsConfig(expected, actual)
  287. assert.Error(t, err)
  288. expected.FsConfig.GCSConfig.AutomaticCredentials = 0
  289. }
  290. func TestGCSWebInvalidFormFile(t *testing.T) {
  291. form := make(url.Values)
  292. form.Set("username", "test_username")
  293. form.Set("fs_provider", "2")
  294. req, _ := http.NewRequest(http.MethodPost, webUserPath, strings.NewReader(form.Encode()))
  295. req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  296. req.ParseForm()
  297. _, err := getFsConfigFromUserPostFields(req)
  298. assert.EqualError(t, err, http.ErrNotMultipart.Error())
  299. }
  300. func TestApiCallsWithBadURL(t *testing.T) {
  301. oldBaseURL := httpBaseURL
  302. oldAuthUsername := authUsername
  303. oldAuthPassword := authPassword
  304. SetBaseURLAndCredentials(invalidURL, oldAuthUsername, oldAuthPassword)
  305. u := dataprovider.User{}
  306. _, _, err := UpdateUser(u, http.StatusBadRequest)
  307. assert.Error(t, err)
  308. _, err = RemoveUser(u, http.StatusNotFound)
  309. assert.Error(t, err)
  310. _, _, err = GetUsers(1, 0, "", http.StatusBadRequest)
  311. assert.Error(t, err)
  312. _, err = CloseConnection("non_existent_id", http.StatusNotFound)
  313. assert.Error(t, err)
  314. _, _, err = Dumpdata("backup.json", "", http.StatusBadRequest)
  315. assert.Error(t, err)
  316. _, _, err = Loaddata("/tmp/backup.json", "", "", http.StatusBadRequest)
  317. assert.Error(t, err)
  318. SetBaseURLAndCredentials(oldBaseURL, oldAuthUsername, oldAuthPassword)
  319. }
  320. func TestApiCallToNotListeningServer(t *testing.T) {
  321. oldBaseURL := httpBaseURL
  322. oldAuthUsername := authUsername
  323. oldAuthPassword := authPassword
  324. SetBaseURLAndCredentials(inactiveURL, oldAuthUsername, oldAuthPassword)
  325. u := dataprovider.User{}
  326. _, _, err := AddUser(u, http.StatusBadRequest)
  327. assert.Error(t, err)
  328. _, _, err = UpdateUser(u, http.StatusNotFound)
  329. assert.Error(t, err)
  330. _, err = RemoveUser(u, http.StatusNotFound)
  331. assert.Error(t, err)
  332. _, _, err = GetUserByID(-1, http.StatusNotFound)
  333. assert.Error(t, err)
  334. _, _, err = GetUsers(100, 0, "", http.StatusOK)
  335. assert.Error(t, err)
  336. _, _, err = GetQuotaScans(http.StatusOK)
  337. assert.Error(t, err)
  338. _, err = StartQuotaScan(u, http.StatusNotFound)
  339. assert.Error(t, err)
  340. _, _, err = GetConnections(http.StatusOK)
  341. assert.Error(t, err)
  342. _, err = CloseConnection("non_existent_id", http.StatusNotFound)
  343. assert.Error(t, err)
  344. _, _, err = GetVersion(http.StatusOK)
  345. assert.Error(t, err)
  346. _, _, err = GetProviderStatus(http.StatusOK)
  347. assert.Error(t, err)
  348. _, _, err = Dumpdata("backup.json", "0", http.StatusOK)
  349. assert.Error(t, err)
  350. _, _, err = Loaddata("/tmp/backup.json", "", "", http.StatusOK)
  351. assert.Error(t, err)
  352. SetBaseURLAndCredentials(oldBaseURL, oldAuthUsername, oldAuthPassword)
  353. }
  354. func TestBasicAuth(t *testing.T) {
  355. oldAuthUsername := authUsername
  356. oldAuthPassword := authPassword
  357. authUserFile := filepath.Join(os.TempDir(), "http_users.txt")
  358. authUserData := []byte("test1:$2y$05$bcHSED7aO1cfLto6ZdDBOOKzlwftslVhtpIkRhAtSa4GuLmk5mola\n")
  359. ioutil.WriteFile(authUserFile, authUserData, 0666)
  360. httpAuth, _ = newBasicAuthProvider(authUserFile)
  361. _, _, err := GetVersion(http.StatusUnauthorized)
  362. assert.NoError(t, err)
  363. SetBaseURLAndCredentials(httpBaseURL, "test1", "password1")
  364. _, _, err = GetVersion(http.StatusOK)
  365. assert.NoError(t, err)
  366. SetBaseURLAndCredentials(httpBaseURL, "test1", "wrong_password")
  367. resp, _ := sendHTTPRequest(http.MethodGet, buildURLRelativeToBase(metricsPath), nil, "")
  368. defer resp.Body.Close()
  369. assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
  370. authUserData = append(authUserData, []byte("test2:$apr1$gLnIkRIf$Xr/6aJfmIrihP4b2N2tcs/\n")...)
  371. err = ioutil.WriteFile(authUserFile, authUserData, 0666)
  372. assert.NoError(t, err)
  373. SetBaseURLAndCredentials(httpBaseURL, "test2", "password2")
  374. _, _, err = GetVersion(http.StatusOK)
  375. assert.NoError(t, err)
  376. SetBaseURLAndCredentials(httpBaseURL, "test2", "wrong_password")
  377. _, _, err = GetVersion(http.StatusOK)
  378. assert.Error(t, err)
  379. authUserData = append(authUserData, []byte("test3:$apr1$gLnIkRIf$Xr/6$aJfmIr$ihP4b2N2tcs/\n")...)
  380. ioutil.WriteFile(authUserFile, authUserData, 0666)
  381. SetBaseURLAndCredentials(httpBaseURL, "test3", "wrong_password")
  382. _, _, err = GetVersion(http.StatusUnauthorized)
  383. assert.NoError(t, err)
  384. authUserData = append(authUserData, []byte("test4:$invalid$gLnIkRIf$Xr/6$aJfmIr$ihP4b2N2tcs/\n")...)
  385. ioutil.WriteFile(authUserFile, authUserData, 0666)
  386. SetBaseURLAndCredentials(httpBaseURL, "test3", "password2")
  387. _, _, err = GetVersion(http.StatusUnauthorized)
  388. assert.NoError(t, err)
  389. if runtime.GOOS != "windows" {
  390. authUserData = append(authUserData, []byte("test5:$apr1$gLnIkRIf$Xr/6aJfmIrihP4b2N2tcs/\n")...)
  391. err = ioutil.WriteFile(authUserFile, authUserData, 0666)
  392. assert.NoError(t, err)
  393. err = os.Chmod(authUserFile, 0001)
  394. assert.NoError(t, err)
  395. SetBaseURLAndCredentials(httpBaseURL, "test5", "password2")
  396. _, _, err = GetVersion(http.StatusUnauthorized)
  397. assert.NoError(t, err)
  398. err = os.Chmod(authUserFile, 0666)
  399. assert.NoError(t, err)
  400. }
  401. authUserData = append(authUserData, []byte("\"foo\"bar\"\r\n")...)
  402. err = ioutil.WriteFile(authUserFile, authUserData, 0666)
  403. assert.NoError(t, err)
  404. SetBaseURLAndCredentials(httpBaseURL, "test2", "password2")
  405. _, _, err = GetVersion(http.StatusUnauthorized)
  406. assert.NoError(t, err)
  407. err = os.Remove(authUserFile)
  408. assert.NoError(t, err)
  409. SetBaseURLAndCredentials(httpBaseURL, oldAuthUsername, oldAuthPassword)
  410. httpAuth, _ = newBasicAuthProvider("")
  411. }
  412. func TestCloseConnectionHandler(t *testing.T) {
  413. req, _ := http.NewRequest(http.MethodDelete, activeConnectionsPath+"/connectionID", nil)
  414. rctx := chi.NewRouteContext()
  415. rctx.URLParams.Add("connectionID", "")
  416. req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
  417. rr := httptest.NewRecorder()
  418. handleCloseConnection(rr, req)
  419. assert.Equal(t, http.StatusBadRequest, rr.Code)
  420. }
  421. func TestRenderInvalidTemplate(t *testing.T) {
  422. tmpl, err := template.New("test").Parse("{{.Count}}")
  423. if assert.NoError(t, err) {
  424. templates["no_match"] = tmpl
  425. rw := httptest.NewRecorder()
  426. renderTemplate(rw, "no_match", map[string]string{})
  427. assert.Equal(t, http.StatusInternalServerError, rw.Code)
  428. }
  429. }
  430. func TestQuotaScanInvalidFs(t *testing.T) {
  431. user := dataprovider.User{
  432. Username: "test",
  433. HomeDir: os.TempDir(),
  434. FsConfig: dataprovider.Filesystem{
  435. Provider: 1,
  436. },
  437. }
  438. sftpd.AddQuotaScan(user.Username)
  439. err := doQuotaScan(user)
  440. assert.Error(t, err)
  441. }