api_http_user.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. package httpd
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "os"
  9. "path"
  10. "time"
  11. "github.com/go-chi/render"
  12. "github.com/rs/xid"
  13. "github.com/drakkan/sftpgo/v2/common"
  14. "github.com/drakkan/sftpgo/v2/dataprovider"
  15. "github.com/drakkan/sftpgo/v2/logger"
  16. "github.com/drakkan/sftpgo/v2/util"
  17. )
  18. func getUserConnection(w http.ResponseWriter, r *http.Request) (*Connection, error) {
  19. claims, err := getTokenClaims(r)
  20. if err != nil || claims.Username == "" {
  21. sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
  22. return nil, fmt.Errorf("invalid token claims %w", err)
  23. }
  24. user, err := dataprovider.UserExists(claims.Username)
  25. if err != nil {
  26. sendAPIResponse(w, r, nil, "Unable to retrieve your user", getRespStatus(err))
  27. return nil, err
  28. }
  29. connID := xid.New().String()
  30. connectionID := fmt.Sprintf("%v_%v", common.ProtocolHTTP, connID)
  31. if err := checkHTTPClientUser(&user, r, connectionID); err != nil {
  32. sendAPIResponse(w, r, err, http.StatusText(http.StatusForbidden), http.StatusForbidden)
  33. return nil, err
  34. }
  35. connection := &Connection{
  36. BaseConnection: common.NewBaseConnection(connID, common.ProtocolHTTP, r.RemoteAddr, user),
  37. request: r,
  38. }
  39. return connection, nil
  40. }
  41. func readUserFolder(w http.ResponseWriter, r *http.Request) {
  42. connection, err := getUserConnection(w, r)
  43. if err != nil {
  44. return
  45. }
  46. common.Connections.Add(connection)
  47. defer common.Connections.Remove(connection.GetID())
  48. name := util.CleanPath(r.URL.Query().Get("path"))
  49. contents, err := connection.ReadDir(name)
  50. if err != nil {
  51. sendAPIResponse(w, r, err, "Unable to get directory contents", getMappedStatusCode(err))
  52. return
  53. }
  54. results := make([]map[string]interface{}, 0, len(contents))
  55. for _, info := range contents {
  56. res := make(map[string]interface{})
  57. res["name"] = info.Name()
  58. if info.Mode().IsRegular() {
  59. res["size"] = info.Size()
  60. }
  61. res["mode"] = info.Mode()
  62. res["last_modified"] = info.ModTime().UTC().Format(time.RFC3339)
  63. results = append(results, res)
  64. }
  65. render.JSON(w, r, results)
  66. }
  67. func createUserDir(w http.ResponseWriter, r *http.Request) {
  68. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  69. connection, err := getUserConnection(w, r)
  70. if err != nil {
  71. return
  72. }
  73. common.Connections.Add(connection)
  74. defer common.Connections.Remove(connection.GetID())
  75. name := util.CleanPath(r.URL.Query().Get("path"))
  76. err = connection.CreateDir(name)
  77. if err != nil {
  78. sendAPIResponse(w, r, err, fmt.Sprintf("Unable to create directory %#v", name), getMappedStatusCode(err))
  79. return
  80. }
  81. sendAPIResponse(w, r, nil, fmt.Sprintf("Directory %#v created", name), http.StatusCreated)
  82. }
  83. func renameUserDir(w http.ResponseWriter, r *http.Request) {
  84. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  85. connection, err := getUserConnection(w, r)
  86. if err != nil {
  87. return
  88. }
  89. common.Connections.Add(connection)
  90. defer common.Connections.Remove(connection.GetID())
  91. oldName := util.CleanPath(r.URL.Query().Get("path"))
  92. newName := util.CleanPath(r.URL.Query().Get("target"))
  93. err = connection.Rename(oldName, newName)
  94. if err != nil {
  95. sendAPIResponse(w, r, err, fmt.Sprintf("Unable to rename directory %#v to %#v", oldName, newName),
  96. getMappedStatusCode(err))
  97. return
  98. }
  99. sendAPIResponse(w, r, nil, fmt.Sprintf("Directory %#v renamed to %#v", oldName, newName), http.StatusOK)
  100. }
  101. func deleteUserDir(w http.ResponseWriter, r *http.Request) {
  102. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  103. connection, err := getUserConnection(w, r)
  104. if err != nil {
  105. return
  106. }
  107. common.Connections.Add(connection)
  108. defer common.Connections.Remove(connection.GetID())
  109. name := util.CleanPath(r.URL.Query().Get("path"))
  110. err = connection.RemoveDir(name)
  111. if err != nil {
  112. sendAPIResponse(w, r, err, fmt.Sprintf("Unable to delete directory %#v", name), getMappedStatusCode(err))
  113. return
  114. }
  115. sendAPIResponse(w, r, nil, fmt.Sprintf("Directory %#v deleted", name), http.StatusOK)
  116. }
  117. func getUserFile(w http.ResponseWriter, r *http.Request) {
  118. connection, err := getUserConnection(w, r)
  119. if err != nil {
  120. return
  121. }
  122. common.Connections.Add(connection)
  123. defer common.Connections.Remove(connection.GetID())
  124. name := util.CleanPath(r.URL.Query().Get("path"))
  125. if name == "/" {
  126. sendAPIResponse(w, r, nil, "Please set the path to a valid file", http.StatusBadRequest)
  127. return
  128. }
  129. info, err := connection.Stat(name, 0)
  130. if err != nil {
  131. sendAPIResponse(w, r, err, "Unable to stat the requested file", getMappedStatusCode(err))
  132. return
  133. }
  134. if info.IsDir() {
  135. sendAPIResponse(w, r, nil, fmt.Sprintf("Please set the path to a valid file, %#v is a directory", name), http.StatusBadRequest)
  136. return
  137. }
  138. if status, err := downloadFile(w, r, connection, name, info); err != nil {
  139. resp := apiResponse{
  140. Error: err.Error(),
  141. Message: http.StatusText(status),
  142. }
  143. ctx := r.Context()
  144. if status != 0 {
  145. ctx = context.WithValue(ctx, render.StatusCtxKey, status)
  146. }
  147. render.JSON(w, r.WithContext(ctx), resp)
  148. }
  149. }
  150. func uploadUserFiles(w http.ResponseWriter, r *http.Request) {
  151. if maxUploadFileSize > 0 {
  152. r.Body = http.MaxBytesReader(w, r.Body, maxUploadFileSize)
  153. }
  154. connection, err := getUserConnection(w, r)
  155. if err != nil {
  156. return
  157. }
  158. common.Connections.Add(connection)
  159. defer common.Connections.Remove(connection.GetID())
  160. err = r.ParseMultipartForm(maxMultipartMem)
  161. if err != nil {
  162. sendAPIResponse(w, r, err, "Unable to parse multipart form", http.StatusBadRequest)
  163. return
  164. }
  165. defer r.MultipartForm.RemoveAll() //nolint:errcheck
  166. parentDir := util.CleanPath(r.URL.Query().Get("path"))
  167. files := r.MultipartForm.File["filename"]
  168. if len(files) == 0 {
  169. sendAPIResponse(w, r, err, "No files uploaded!", http.StatusBadRequest)
  170. return
  171. }
  172. for _, f := range files {
  173. file, err := f.Open()
  174. if err != nil {
  175. sendAPIResponse(w, r, err, fmt.Sprintf("Unable to read uploaded file %#v", f.Filename), getMappedStatusCode(err))
  176. return
  177. }
  178. defer file.Close()
  179. filePath := path.Join(parentDir, f.Filename)
  180. writer, err := connection.getFileWriter(filePath)
  181. if err != nil {
  182. sendAPIResponse(w, r, err, fmt.Sprintf("Unable to write file %#v", f.Filename), getMappedStatusCode(err))
  183. return
  184. }
  185. _, err = io.Copy(writer, file)
  186. if err != nil {
  187. writer.Close() //nolint:errcheck
  188. sendAPIResponse(w, r, err, fmt.Sprintf("Error saving file %#v", f.Filename), getMappedStatusCode(err))
  189. return
  190. }
  191. err = writer.Close()
  192. if err != nil {
  193. sendAPIResponse(w, r, err, fmt.Sprintf("Error closing file %#v", f.Filename), getMappedStatusCode(err))
  194. return
  195. }
  196. }
  197. sendAPIResponse(w, r, nil, "Upload completed", http.StatusCreated)
  198. }
  199. func renameUserFile(w http.ResponseWriter, r *http.Request) {
  200. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  201. connection, err := getUserConnection(w, r)
  202. if err != nil {
  203. return
  204. }
  205. common.Connections.Add(connection)
  206. defer common.Connections.Remove(connection.GetID())
  207. oldName := util.CleanPath(r.URL.Query().Get("path"))
  208. newName := util.CleanPath(r.URL.Query().Get("target"))
  209. err = connection.Rename(oldName, newName)
  210. if err != nil {
  211. sendAPIResponse(w, r, err, fmt.Sprintf("Unable to rename file %#v to %#v", oldName, newName),
  212. getMappedStatusCode(err))
  213. return
  214. }
  215. sendAPIResponse(w, r, nil, fmt.Sprintf("File %#v renamed to %#v", oldName, newName), http.StatusOK)
  216. }
  217. func deleteUserFile(w http.ResponseWriter, r *http.Request) {
  218. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  219. connection, err := getUserConnection(w, r)
  220. if err != nil {
  221. return
  222. }
  223. common.Connections.Add(connection)
  224. defer common.Connections.Remove(connection.GetID())
  225. name := util.CleanPath(r.URL.Query().Get("path"))
  226. fs, p, err := connection.GetFsAndResolvedPath(name)
  227. if err != nil {
  228. sendAPIResponse(w, r, err, fmt.Sprintf("Unable to delete file %#v", name), getMappedStatusCode(err))
  229. return
  230. }
  231. var fi os.FileInfo
  232. if fi, err = fs.Lstat(p); err != nil {
  233. connection.Log(logger.LevelWarn, "failed to remove a file %#v: stat error: %+v", p, err)
  234. err = connection.GetFsError(fs, err)
  235. sendAPIResponse(w, r, err, fmt.Sprintf("Unable to delete file %#v", name), getMappedStatusCode(err))
  236. return
  237. }
  238. if fi.IsDir() && fi.Mode()&os.ModeSymlink == 0 {
  239. connection.Log(logger.LevelDebug, "cannot remove %#v is not a file/symlink", p)
  240. sendAPIResponse(w, r, err, fmt.Sprintf("Unable delete %#v, it is not a file/symlink", name), http.StatusBadRequest)
  241. return
  242. }
  243. err = connection.RemoveFile(fs, p, name, fi)
  244. if err != nil {
  245. sendAPIResponse(w, r, err, fmt.Sprintf("Unable to delete file %#v", name), getMappedStatusCode(err))
  246. return
  247. }
  248. sendAPIResponse(w, r, nil, fmt.Sprintf("File %#v deleted", name), http.StatusOK)
  249. }
  250. func getUserFilesAsZipStream(w http.ResponseWriter, r *http.Request) {
  251. connection, err := getUserConnection(w, r)
  252. if err != nil {
  253. return
  254. }
  255. common.Connections.Add(connection)
  256. defer common.Connections.Remove(connection.GetID())
  257. var filesList []string
  258. err = render.DecodeJSON(r.Body, &filesList)
  259. if err != nil {
  260. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  261. return
  262. }
  263. baseDir := "/"
  264. for idx := range filesList {
  265. filesList[idx] = util.CleanPath(filesList[idx])
  266. }
  267. w.Header().Set("Content-Disposition", "attachment; filename=\"sftpgo-download.zip\"")
  268. renderCompressedFiles(w, connection, baseDir, filesList)
  269. }
  270. func getUserPublicKeys(w http.ResponseWriter, r *http.Request) {
  271. claims, err := getTokenClaims(r)
  272. if err != nil || claims.Username == "" {
  273. sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
  274. return
  275. }
  276. user, err := dataprovider.UserExists(claims.Username)
  277. if err != nil {
  278. sendAPIResponse(w, r, nil, "Unable to retrieve your user", getRespStatus(err))
  279. return
  280. }
  281. render.JSON(w, r, user.PublicKeys)
  282. }
  283. func setUserPublicKeys(w http.ResponseWriter, r *http.Request) {
  284. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  285. claims, err := getTokenClaims(r)
  286. if err != nil || claims.Username == "" {
  287. sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
  288. return
  289. }
  290. user, err := dataprovider.UserExists(claims.Username)
  291. if err != nil {
  292. sendAPIResponse(w, r, nil, "Unable to retrieve your user", getRespStatus(err))
  293. return
  294. }
  295. var publicKeys []string
  296. err = render.DecodeJSON(r.Body, &publicKeys)
  297. if err != nil {
  298. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  299. return
  300. }
  301. user.PublicKeys = publicKeys
  302. err = dataprovider.UpdateUser(&user)
  303. if err != nil {
  304. sendAPIResponse(w, r, err, "", getRespStatus(err))
  305. return
  306. }
  307. sendAPIResponse(w, r, err, "Public keys updated", http.StatusOK)
  308. }
  309. func changeUserPassword(w http.ResponseWriter, r *http.Request) {
  310. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  311. var pwd pwdChange
  312. err := render.DecodeJSON(r.Body, &pwd)
  313. if err != nil {
  314. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  315. return
  316. }
  317. err = doChangeUserPassword(r, pwd.CurrentPassword, pwd.NewPassword, pwd.NewPassword)
  318. if err != nil {
  319. sendAPIResponse(w, r, err, "", getRespStatus(err))
  320. return
  321. }
  322. sendAPIResponse(w, r, err, "Password updated", http.StatusOK)
  323. }
  324. func doChangeUserPassword(r *http.Request, currentPassword, newPassword, confirmNewPassword string) error {
  325. if currentPassword == "" || newPassword == "" || confirmNewPassword == "" {
  326. return util.NewValidationError("please provide the current password and the new one two times")
  327. }
  328. if newPassword != confirmNewPassword {
  329. return util.NewValidationError("the two password fields do not match")
  330. }
  331. if currentPassword == newPassword {
  332. return util.NewValidationError("the new password must be different from the current one")
  333. }
  334. claims, err := getTokenClaims(r)
  335. if err != nil || claims.Username == "" {
  336. return errors.New("invalid token claims")
  337. }
  338. user, err := dataprovider.CheckUserAndPass(claims.Username, currentPassword, util.GetIPFromRemoteAddress(r.RemoteAddr),
  339. common.ProtocolHTTP)
  340. if err != nil {
  341. return util.NewValidationError("current password does not match")
  342. }
  343. user.Password = newPassword
  344. return dataprovider.UpdateUser(&user)
  345. }