api_utils.go 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034
  1. package httpd
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "io/ioutil"
  10. "net/http"
  11. "net/url"
  12. "os"
  13. "path"
  14. "path/filepath"
  15. "strconv"
  16. "strings"
  17. "github.com/go-chi/render"
  18. "github.com/drakkan/sftpgo/common"
  19. "github.com/drakkan/sftpgo/dataprovider"
  20. "github.com/drakkan/sftpgo/httpclient"
  21. "github.com/drakkan/sftpgo/kms"
  22. "github.com/drakkan/sftpgo/utils"
  23. "github.com/drakkan/sftpgo/version"
  24. "github.com/drakkan/sftpgo/vfs"
  25. )
  26. var (
  27. httpBaseURL = "http://127.0.0.1:8080"
  28. authUsername = ""
  29. authPassword = ""
  30. )
  31. // SetBaseURLAndCredentials sets the base url and the optional credentials to use for HTTP requests.
  32. // Default URL is "http://127.0.0.1:8080" with empty credentials
  33. func SetBaseURLAndCredentials(url, username, password string) {
  34. httpBaseURL = url
  35. authUsername = username
  36. authPassword = password
  37. }
  38. func sendHTTPRequest(method, url string, body io.Reader, contentType string) (*http.Response, error) {
  39. req, err := http.NewRequest(method, url, body)
  40. if err != nil {
  41. return nil, err
  42. }
  43. if contentType != "" {
  44. req.Header.Set("Content-Type", "application/json")
  45. }
  46. if authUsername != "" || authPassword != "" {
  47. req.SetBasicAuth(authUsername, authPassword)
  48. }
  49. return httpclient.GetHTTPClient().Do(req)
  50. }
  51. func buildURLRelativeToBase(paths ...string) string {
  52. // we need to use path.Join and not filepath.Join
  53. // since filepath.Join will use backslash separator on Windows
  54. p := path.Join(paths...)
  55. return fmt.Sprintf("%s/%s", strings.TrimRight(httpBaseURL, "/"), strings.TrimLeft(p, "/"))
  56. }
  57. func sendAPIResponse(w http.ResponseWriter, r *http.Request, err error, message string, code int) {
  58. var errorString string
  59. if err != nil {
  60. errorString = err.Error()
  61. }
  62. resp := apiResponse{
  63. Error: errorString,
  64. Message: message,
  65. }
  66. ctx := context.WithValue(r.Context(), render.StatusCtxKey, code)
  67. render.JSON(w, r.WithContext(ctx), resp)
  68. }
  69. func getRespStatus(err error) int {
  70. if _, ok := err.(*dataprovider.ValidationError); ok {
  71. return http.StatusBadRequest
  72. }
  73. if _, ok := err.(*dataprovider.MethodDisabledError); ok {
  74. return http.StatusForbidden
  75. }
  76. if _, ok := err.(*dataprovider.RecordNotFoundError); ok {
  77. return http.StatusNotFound
  78. }
  79. if os.IsNotExist(err) {
  80. return http.StatusBadRequest
  81. }
  82. return http.StatusInternalServerError
  83. }
  84. // AddUser adds a new user and checks the received HTTP Status code against expectedStatusCode.
  85. func AddUser(user dataprovider.User, expectedStatusCode int) (dataprovider.User, []byte, error) {
  86. var newUser dataprovider.User
  87. var body []byte
  88. userAsJSON, _ := json.Marshal(user)
  89. resp, err := sendHTTPRequest(http.MethodPost, buildURLRelativeToBase(userPath), bytes.NewBuffer(userAsJSON),
  90. "application/json")
  91. if err != nil {
  92. return newUser, body, err
  93. }
  94. defer resp.Body.Close()
  95. err = checkResponse(resp.StatusCode, expectedStatusCode)
  96. if expectedStatusCode != http.StatusOK {
  97. body, _ = getResponseBody(resp)
  98. return newUser, body, err
  99. }
  100. if err == nil {
  101. err = render.DecodeJSON(resp.Body, &newUser)
  102. } else {
  103. body, _ = getResponseBody(resp)
  104. }
  105. if err == nil {
  106. err = checkUser(&user, &newUser)
  107. }
  108. return newUser, body, err
  109. }
  110. // UpdateUser updates an existing user and checks the received HTTP Status code against expectedStatusCode.
  111. func UpdateUser(user dataprovider.User, expectedStatusCode int, disconnect string) (dataprovider.User, []byte, error) {
  112. var newUser dataprovider.User
  113. var body []byte
  114. url, err := addDisconnectQueryParam(buildURLRelativeToBase(userPath, strconv.FormatInt(user.ID, 10)), disconnect)
  115. if err != nil {
  116. return user, body, err
  117. }
  118. userAsJSON, _ := json.Marshal(user)
  119. resp, err := sendHTTPRequest(http.MethodPut, url.String(), bytes.NewBuffer(userAsJSON), "application/json")
  120. if err != nil {
  121. return user, body, err
  122. }
  123. defer resp.Body.Close()
  124. body, _ = getResponseBody(resp)
  125. err = checkResponse(resp.StatusCode, expectedStatusCode)
  126. if expectedStatusCode != http.StatusOK {
  127. return newUser, body, err
  128. }
  129. if err == nil {
  130. newUser, body, err = GetUserByID(user.ID, expectedStatusCode)
  131. }
  132. if err == nil {
  133. err = checkUser(&user, &newUser)
  134. }
  135. return newUser, body, err
  136. }
  137. // RemoveUser removes an existing user and checks the received HTTP Status code against expectedStatusCode.
  138. func RemoveUser(user dataprovider.User, expectedStatusCode int) ([]byte, error) {
  139. var body []byte
  140. resp, err := sendHTTPRequest(http.MethodDelete, buildURLRelativeToBase(userPath, strconv.FormatInt(user.ID, 10)), nil, "")
  141. if err != nil {
  142. return body, err
  143. }
  144. defer resp.Body.Close()
  145. body, _ = getResponseBody(resp)
  146. return body, checkResponse(resp.StatusCode, expectedStatusCode)
  147. }
  148. // GetUserByID gets a user by database id and checks the received HTTP Status code against expectedStatusCode.
  149. func GetUserByID(userID int64, expectedStatusCode int) (dataprovider.User, []byte, error) {
  150. var user dataprovider.User
  151. var body []byte
  152. resp, err := sendHTTPRequest(http.MethodGet, buildURLRelativeToBase(userPath, strconv.FormatInt(userID, 10)), nil, "")
  153. if err != nil {
  154. return user, body, err
  155. }
  156. defer resp.Body.Close()
  157. err = checkResponse(resp.StatusCode, expectedStatusCode)
  158. if err == nil && expectedStatusCode == http.StatusOK {
  159. err = render.DecodeJSON(resp.Body, &user)
  160. } else {
  161. body, _ = getResponseBody(resp)
  162. }
  163. return user, body, err
  164. }
  165. // GetUsers returns a list of users and checks the received HTTP Status code against expectedStatusCode.
  166. // The number of results can be limited specifying a limit.
  167. // Some results can be skipped specifying an offset.
  168. // The results can be filtered specifying a username, the username filter is an exact match
  169. func GetUsers(limit, offset int64, username string, expectedStatusCode int) ([]dataprovider.User, []byte, error) {
  170. var users []dataprovider.User
  171. var body []byte
  172. url, err := addLimitAndOffsetQueryParams(buildURLRelativeToBase(userPath), limit, offset)
  173. if err != nil {
  174. return users, body, err
  175. }
  176. if len(username) > 0 {
  177. q := url.Query()
  178. q.Add("username", username)
  179. url.RawQuery = q.Encode()
  180. }
  181. resp, err := sendHTTPRequest(http.MethodGet, url.String(), nil, "")
  182. if err != nil {
  183. return users, body, err
  184. }
  185. defer resp.Body.Close()
  186. err = checkResponse(resp.StatusCode, expectedStatusCode)
  187. if err == nil && expectedStatusCode == http.StatusOK {
  188. err = render.DecodeJSON(resp.Body, &users)
  189. } else {
  190. body, _ = getResponseBody(resp)
  191. }
  192. return users, body, err
  193. }
  194. // GetQuotaScans gets active quota scans for users and checks the received HTTP Status code against expectedStatusCode.
  195. func GetQuotaScans(expectedStatusCode int) ([]common.ActiveQuotaScan, []byte, error) {
  196. var quotaScans []common.ActiveQuotaScan
  197. var body []byte
  198. resp, err := sendHTTPRequest(http.MethodGet, buildURLRelativeToBase(quotaScanPath), nil, "")
  199. if err != nil {
  200. return quotaScans, body, err
  201. }
  202. defer resp.Body.Close()
  203. err = checkResponse(resp.StatusCode, expectedStatusCode)
  204. if err == nil && expectedStatusCode == http.StatusOK {
  205. err = render.DecodeJSON(resp.Body, &quotaScans)
  206. } else {
  207. body, _ = getResponseBody(resp)
  208. }
  209. return quotaScans, body, err
  210. }
  211. // StartQuotaScan starts a new quota scan for the given user and checks the received HTTP Status code against expectedStatusCode.
  212. func StartQuotaScan(user dataprovider.User, expectedStatusCode int) ([]byte, error) {
  213. var body []byte
  214. userAsJSON, _ := json.Marshal(user)
  215. resp, err := sendHTTPRequest(http.MethodPost, buildURLRelativeToBase(quotaScanPath), bytes.NewBuffer(userAsJSON), "")
  216. if err != nil {
  217. return body, err
  218. }
  219. defer resp.Body.Close()
  220. body, _ = getResponseBody(resp)
  221. return body, checkResponse(resp.StatusCode, expectedStatusCode)
  222. }
  223. // UpdateQuotaUsage updates the user used quota limits and checks the received HTTP Status code against expectedStatusCode.
  224. func UpdateQuotaUsage(user dataprovider.User, mode string, expectedStatusCode int) ([]byte, error) {
  225. var body []byte
  226. userAsJSON, _ := json.Marshal(user)
  227. url, err := addModeQueryParam(buildURLRelativeToBase(updateUsedQuotaPath), mode)
  228. if err != nil {
  229. return body, err
  230. }
  231. resp, err := sendHTTPRequest(http.MethodPut, url.String(), bytes.NewBuffer(userAsJSON), "")
  232. if err != nil {
  233. return body, err
  234. }
  235. defer resp.Body.Close()
  236. body, _ = getResponseBody(resp)
  237. return body, checkResponse(resp.StatusCode, expectedStatusCode)
  238. }
  239. // GetConnections returns status and stats for active SFTP/SCP connections
  240. func GetConnections(expectedStatusCode int) ([]common.ConnectionStatus, []byte, error) {
  241. var connections []common.ConnectionStatus
  242. var body []byte
  243. resp, err := sendHTTPRequest(http.MethodGet, buildURLRelativeToBase(activeConnectionsPath), nil, "")
  244. if err != nil {
  245. return connections, body, err
  246. }
  247. defer resp.Body.Close()
  248. err = checkResponse(resp.StatusCode, expectedStatusCode)
  249. if err == nil && expectedStatusCode == http.StatusOK {
  250. err = render.DecodeJSON(resp.Body, &connections)
  251. } else {
  252. body, _ = getResponseBody(resp)
  253. }
  254. return connections, body, err
  255. }
  256. // CloseConnection closes an active connection identified by connectionID
  257. func CloseConnection(connectionID string, expectedStatusCode int) ([]byte, error) {
  258. var body []byte
  259. resp, err := sendHTTPRequest(http.MethodDelete, buildURLRelativeToBase(activeConnectionsPath, connectionID), nil, "")
  260. if err != nil {
  261. return body, err
  262. }
  263. defer resp.Body.Close()
  264. err = checkResponse(resp.StatusCode, expectedStatusCode)
  265. body, _ = getResponseBody(resp)
  266. return body, err
  267. }
  268. // AddFolder adds a new folder and checks the received HTTP Status code against expectedStatusCode
  269. func AddFolder(folder vfs.BaseVirtualFolder, expectedStatusCode int) (vfs.BaseVirtualFolder, []byte, error) {
  270. var newFolder vfs.BaseVirtualFolder
  271. var body []byte
  272. folderAsJSON, _ := json.Marshal(folder)
  273. resp, err := sendHTTPRequest(http.MethodPost, buildURLRelativeToBase(folderPath), bytes.NewBuffer(folderAsJSON),
  274. "application/json")
  275. if err != nil {
  276. return newFolder, body, err
  277. }
  278. defer resp.Body.Close()
  279. err = checkResponse(resp.StatusCode, expectedStatusCode)
  280. if expectedStatusCode != http.StatusOK {
  281. body, _ = getResponseBody(resp)
  282. return newFolder, body, err
  283. }
  284. if err == nil {
  285. err = render.DecodeJSON(resp.Body, &newFolder)
  286. } else {
  287. body, _ = getResponseBody(resp)
  288. }
  289. if err == nil {
  290. err = checkFolder(&folder, &newFolder)
  291. }
  292. return newFolder, body, err
  293. }
  294. // RemoveFolder removes an existing user and checks the received HTTP Status code against expectedStatusCode.
  295. func RemoveFolder(folder vfs.BaseVirtualFolder, expectedStatusCode int) ([]byte, error) {
  296. var body []byte
  297. baseURL := buildURLRelativeToBase(folderPath)
  298. url, err := url.Parse(baseURL)
  299. if err != nil {
  300. return body, err
  301. }
  302. q := url.Query()
  303. q.Add("folder_path", folder.MappedPath)
  304. url.RawQuery = q.Encode()
  305. resp, err := sendHTTPRequest(http.MethodDelete, url.String(), nil, "")
  306. if err != nil {
  307. return body, err
  308. }
  309. defer resp.Body.Close()
  310. body, _ = getResponseBody(resp)
  311. return body, checkResponse(resp.StatusCode, expectedStatusCode)
  312. }
  313. // GetFolders returns a list of folders and checks the received HTTP Status code against expectedStatusCode.
  314. // The number of results can be limited specifying a limit.
  315. // Some results can be skipped specifying an offset.
  316. // The results can be filtered specifying a folder path, the folder path filter is an exact match
  317. func GetFolders(limit int64, offset int64, mappedPath string, expectedStatusCode int) ([]vfs.BaseVirtualFolder, []byte, error) {
  318. var folders []vfs.BaseVirtualFolder
  319. var body []byte
  320. url, err := addLimitAndOffsetQueryParams(buildURLRelativeToBase(folderPath), limit, offset)
  321. if err != nil {
  322. return folders, body, err
  323. }
  324. if len(mappedPath) > 0 {
  325. q := url.Query()
  326. q.Add("folder_path", mappedPath)
  327. url.RawQuery = q.Encode()
  328. }
  329. resp, err := sendHTTPRequest(http.MethodGet, url.String(), nil, "")
  330. if err != nil {
  331. return folders, body, err
  332. }
  333. defer resp.Body.Close()
  334. err = checkResponse(resp.StatusCode, expectedStatusCode)
  335. if err == nil && expectedStatusCode == http.StatusOK {
  336. err = render.DecodeJSON(resp.Body, &folders)
  337. } else {
  338. body, _ = getResponseBody(resp)
  339. }
  340. return folders, body, err
  341. }
  342. // GetFoldersQuotaScans gets active quota scans for folders and checks the received HTTP Status code against expectedStatusCode.
  343. func GetFoldersQuotaScans(expectedStatusCode int) ([]common.ActiveVirtualFolderQuotaScan, []byte, error) {
  344. var quotaScans []common.ActiveVirtualFolderQuotaScan
  345. var body []byte
  346. resp, err := sendHTTPRequest(http.MethodGet, buildURLRelativeToBase(quotaScanVFolderPath), nil, "")
  347. if err != nil {
  348. return quotaScans, body, err
  349. }
  350. defer resp.Body.Close()
  351. err = checkResponse(resp.StatusCode, expectedStatusCode)
  352. if err == nil && expectedStatusCode == http.StatusOK {
  353. err = render.DecodeJSON(resp.Body, &quotaScans)
  354. } else {
  355. body, _ = getResponseBody(resp)
  356. }
  357. return quotaScans, body, err
  358. }
  359. // StartFolderQuotaScan start a new quota scan for the given folder and checks the received HTTP Status code against expectedStatusCode.
  360. func StartFolderQuotaScan(folder vfs.BaseVirtualFolder, expectedStatusCode int) ([]byte, error) {
  361. var body []byte
  362. folderAsJSON, _ := json.Marshal(folder)
  363. resp, err := sendHTTPRequest(http.MethodPost, buildURLRelativeToBase(quotaScanVFolderPath), bytes.NewBuffer(folderAsJSON), "")
  364. if err != nil {
  365. return body, err
  366. }
  367. defer resp.Body.Close()
  368. body, _ = getResponseBody(resp)
  369. return body, checkResponse(resp.StatusCode, expectedStatusCode)
  370. }
  371. // UpdateFolderQuotaUsage updates the folder used quota limits and checks the received HTTP Status code against expectedStatusCode.
  372. func UpdateFolderQuotaUsage(folder vfs.BaseVirtualFolder, mode string, expectedStatusCode int) ([]byte, error) {
  373. var body []byte
  374. folderAsJSON, _ := json.Marshal(folder)
  375. url, err := addModeQueryParam(buildURLRelativeToBase(updateFolderUsedQuotaPath), mode)
  376. if err != nil {
  377. return body, err
  378. }
  379. resp, err := sendHTTPRequest(http.MethodPut, url.String(), bytes.NewBuffer(folderAsJSON), "")
  380. if err != nil {
  381. return body, err
  382. }
  383. defer resp.Body.Close()
  384. body, _ = getResponseBody(resp)
  385. return body, checkResponse(resp.StatusCode, expectedStatusCode)
  386. }
  387. // GetVersion returns version details
  388. func GetVersion(expectedStatusCode int) (version.Info, []byte, error) {
  389. var appVersion version.Info
  390. var body []byte
  391. resp, err := sendHTTPRequest(http.MethodGet, buildURLRelativeToBase(versionPath), nil, "")
  392. if err != nil {
  393. return appVersion, body, err
  394. }
  395. defer resp.Body.Close()
  396. err = checkResponse(resp.StatusCode, expectedStatusCode)
  397. if err == nil && expectedStatusCode == http.StatusOK {
  398. err = render.DecodeJSON(resp.Body, &appVersion)
  399. } else {
  400. body, _ = getResponseBody(resp)
  401. }
  402. return appVersion, body, err
  403. }
  404. // GetStatus returns the server status
  405. func GetStatus(expectedStatusCode int) (ServicesStatus, []byte, error) {
  406. var response ServicesStatus
  407. var body []byte
  408. resp, err := sendHTTPRequest(http.MethodGet, buildURLRelativeToBase(serverStatusPath), nil, "")
  409. if err != nil {
  410. return response, body, err
  411. }
  412. defer resp.Body.Close()
  413. err = checkResponse(resp.StatusCode, expectedStatusCode)
  414. if err == nil && (expectedStatusCode == http.StatusOK) {
  415. err = render.DecodeJSON(resp.Body, &response)
  416. } else {
  417. body, _ = getResponseBody(resp)
  418. }
  419. return response, body, err
  420. }
  421. // GetBanTime returns the ban time for the given IP address
  422. func GetBanTime(ip string, expectedStatusCode int) (map[string]interface{}, []byte, error) {
  423. var response map[string]interface{}
  424. var body []byte
  425. url, err := url.Parse(buildURLRelativeToBase(defenderBanTime))
  426. if err != nil {
  427. return response, body, err
  428. }
  429. q := url.Query()
  430. q.Add("ip", ip)
  431. url.RawQuery = q.Encode()
  432. resp, err := sendHTTPRequest(http.MethodGet, url.String(), nil, "")
  433. if err != nil {
  434. return response, body, err
  435. }
  436. defer resp.Body.Close()
  437. err = checkResponse(resp.StatusCode, expectedStatusCode)
  438. if err == nil && expectedStatusCode == http.StatusOK {
  439. err = render.DecodeJSON(resp.Body, &response)
  440. } else {
  441. body, _ = getResponseBody(resp)
  442. }
  443. return response, body, err
  444. }
  445. // GetScore returns the score for the given IP address
  446. func GetScore(ip string, expectedStatusCode int) (map[string]interface{}, []byte, error) {
  447. var response map[string]interface{}
  448. var body []byte
  449. url, err := url.Parse(buildURLRelativeToBase(defenderScore))
  450. if err != nil {
  451. return response, body, err
  452. }
  453. q := url.Query()
  454. q.Add("ip", ip)
  455. url.RawQuery = q.Encode()
  456. resp, err := sendHTTPRequest(http.MethodGet, url.String(), nil, "")
  457. if err != nil {
  458. return response, body, err
  459. }
  460. defer resp.Body.Close()
  461. err = checkResponse(resp.StatusCode, expectedStatusCode)
  462. if err == nil && expectedStatusCode == http.StatusOK {
  463. err = render.DecodeJSON(resp.Body, &response)
  464. } else {
  465. body, _ = getResponseBody(resp)
  466. }
  467. return response, body, err
  468. }
  469. // UnbanIP unbans the given IP address
  470. func UnbanIP(ip string, expectedStatusCode int) error {
  471. postBody := make(map[string]string)
  472. postBody["ip"] = ip
  473. asJSON, _ := json.Marshal(postBody)
  474. resp, err := sendHTTPRequest(http.MethodPost, buildURLRelativeToBase(defenderUnban), bytes.NewBuffer(asJSON), "")
  475. if err != nil {
  476. return err
  477. }
  478. defer resp.Body.Close()
  479. return checkResponse(resp.StatusCode, expectedStatusCode)
  480. }
  481. // Dumpdata requests a backup to outputFile.
  482. // outputFile is relative to the configured backups_path
  483. func Dumpdata(outputFile, indent string, expectedStatusCode int) (map[string]interface{}, []byte, error) {
  484. var response map[string]interface{}
  485. var body []byte
  486. url, err := url.Parse(buildURLRelativeToBase(dumpDataPath))
  487. if err != nil {
  488. return response, body, err
  489. }
  490. q := url.Query()
  491. q.Add("output_file", outputFile)
  492. if len(indent) > 0 {
  493. q.Add("indent", indent)
  494. }
  495. url.RawQuery = q.Encode()
  496. resp, err := sendHTTPRequest(http.MethodGet, url.String(), nil, "")
  497. if err != nil {
  498. return response, body, err
  499. }
  500. defer resp.Body.Close()
  501. err = checkResponse(resp.StatusCode, expectedStatusCode)
  502. if err == nil && expectedStatusCode == http.StatusOK {
  503. err = render.DecodeJSON(resp.Body, &response)
  504. } else {
  505. body, _ = getResponseBody(resp)
  506. }
  507. return response, body, err
  508. }
  509. // Loaddata restores a backup.
  510. // New users are added, existing users are updated. Users will be restored one by one and the restore is stopped if a
  511. // user cannot be added/updated, so it could happen a partial restore
  512. func Loaddata(inputFile, scanQuota, mode string, expectedStatusCode int) (map[string]interface{}, []byte, error) {
  513. var response map[string]interface{}
  514. var body []byte
  515. url, err := url.Parse(buildURLRelativeToBase(loadDataPath))
  516. if err != nil {
  517. return response, body, err
  518. }
  519. q := url.Query()
  520. q.Add("input_file", inputFile)
  521. if len(scanQuota) > 0 {
  522. q.Add("scan_quota", scanQuota)
  523. }
  524. if len(mode) > 0 {
  525. q.Add("mode", mode)
  526. }
  527. url.RawQuery = q.Encode()
  528. resp, err := sendHTTPRequest(http.MethodGet, url.String(), nil, "")
  529. if err != nil {
  530. return response, body, err
  531. }
  532. defer resp.Body.Close()
  533. err = checkResponse(resp.StatusCode, expectedStatusCode)
  534. if err == nil && expectedStatusCode == http.StatusOK {
  535. err = render.DecodeJSON(resp.Body, &response)
  536. } else {
  537. body, _ = getResponseBody(resp)
  538. }
  539. return response, body, err
  540. }
  541. func checkResponse(actual int, expected int) error {
  542. if expected != actual {
  543. return fmt.Errorf("wrong status code: got %v want %v", actual, expected)
  544. }
  545. return nil
  546. }
  547. func getResponseBody(resp *http.Response) ([]byte, error) {
  548. return ioutil.ReadAll(resp.Body)
  549. }
  550. func checkFolder(expected *vfs.BaseVirtualFolder, actual *vfs.BaseVirtualFolder) error {
  551. if expected.ID <= 0 {
  552. if actual.ID <= 0 {
  553. return errors.New("actual folder ID must be > 0")
  554. }
  555. } else {
  556. if actual.ID != expected.ID {
  557. return errors.New("folder ID mismatch")
  558. }
  559. }
  560. if expected.MappedPath != actual.MappedPath {
  561. return errors.New("mapped path mismatch")
  562. }
  563. if expected.LastQuotaUpdate != actual.LastQuotaUpdate {
  564. return errors.New("last quota update mismatch")
  565. }
  566. if expected.UsedQuotaSize != actual.UsedQuotaSize {
  567. return errors.New("used quota size mismatch")
  568. }
  569. if expected.UsedQuotaFiles != actual.UsedQuotaFiles {
  570. return errors.New("used quota files mismatch")
  571. }
  572. if len(expected.Users) != len(actual.Users) {
  573. return errors.New("folder users mismatch")
  574. }
  575. for _, u := range actual.Users {
  576. if !utils.IsStringInSlice(u, expected.Users) {
  577. return errors.New("folder users mismatch")
  578. }
  579. }
  580. return nil
  581. }
  582. func checkUser(expected *dataprovider.User, actual *dataprovider.User) error {
  583. if actual.Password != "" {
  584. return errors.New("User password must not be visible")
  585. }
  586. if expected.ID <= 0 {
  587. if actual.ID <= 0 {
  588. return errors.New("actual user ID must be > 0")
  589. }
  590. } else {
  591. if actual.ID != expected.ID {
  592. return errors.New("user ID mismatch")
  593. }
  594. }
  595. if len(expected.Permissions) != len(actual.Permissions) {
  596. return errors.New("Permissions mismatch")
  597. }
  598. for dir, perms := range expected.Permissions {
  599. if actualPerms, ok := actual.Permissions[dir]; ok {
  600. for _, v := range actualPerms {
  601. if !utils.IsStringInSlice(v, perms) {
  602. return errors.New("Permissions contents mismatch")
  603. }
  604. }
  605. } else {
  606. return errors.New("Permissions directories mismatch")
  607. }
  608. }
  609. if err := compareUserFilters(expected, actual); err != nil {
  610. return err
  611. }
  612. if err := compareUserFsConfig(expected, actual); err != nil {
  613. return err
  614. }
  615. if err := compareUserVirtualFolders(expected, actual); err != nil {
  616. return err
  617. }
  618. return compareEqualsUserFields(expected, actual)
  619. }
  620. func compareUserVirtualFolders(expected *dataprovider.User, actual *dataprovider.User) error {
  621. if len(actual.VirtualFolders) != len(expected.VirtualFolders) {
  622. return errors.New("Virtual folders mismatch")
  623. }
  624. for _, v := range actual.VirtualFolders {
  625. found := false
  626. for _, v1 := range expected.VirtualFolders {
  627. if path.Clean(v.VirtualPath) == path.Clean(v1.VirtualPath) &&
  628. filepath.Clean(v.MappedPath) == filepath.Clean(v1.MappedPath) {
  629. found = true
  630. break
  631. }
  632. }
  633. if !found {
  634. return errors.New("Virtual folders mismatch")
  635. }
  636. }
  637. return nil
  638. }
  639. func compareUserFsConfig(expected *dataprovider.User, actual *dataprovider.User) error {
  640. if expected.FsConfig.Provider != actual.FsConfig.Provider {
  641. return errors.New("Fs provider mismatch")
  642. }
  643. if err := compareS3Config(expected, actual); err != nil {
  644. return err
  645. }
  646. if err := compareGCSConfig(expected, actual); err != nil {
  647. return err
  648. }
  649. if err := compareAzBlobConfig(expected, actual); err != nil {
  650. return err
  651. }
  652. if err := checkEncryptedSecret(expected.FsConfig.CryptConfig.Passphrase, actual.FsConfig.CryptConfig.Passphrase); err != nil {
  653. return err
  654. }
  655. if err := compareSFTPFsConfig(expected, actual); err != nil {
  656. return err
  657. }
  658. return nil
  659. }
  660. func compareS3Config(expected *dataprovider.User, actual *dataprovider.User) error {
  661. if expected.FsConfig.S3Config.Bucket != actual.FsConfig.S3Config.Bucket {
  662. return errors.New("S3 bucket mismatch")
  663. }
  664. if expected.FsConfig.S3Config.Region != actual.FsConfig.S3Config.Region {
  665. return errors.New("S3 region mismatch")
  666. }
  667. if expected.FsConfig.S3Config.AccessKey != actual.FsConfig.S3Config.AccessKey {
  668. return errors.New("S3 access key mismatch")
  669. }
  670. if err := checkEncryptedSecret(expected.FsConfig.S3Config.AccessSecret, actual.FsConfig.S3Config.AccessSecret); err != nil {
  671. return fmt.Errorf("S3 access secret mismatch: %v", err)
  672. }
  673. if expected.FsConfig.S3Config.Endpoint != actual.FsConfig.S3Config.Endpoint {
  674. return errors.New("S3 endpoint mismatch")
  675. }
  676. if expected.FsConfig.S3Config.StorageClass != actual.FsConfig.S3Config.StorageClass {
  677. return errors.New("S3 storage class mismatch")
  678. }
  679. if expected.FsConfig.S3Config.UploadPartSize != actual.FsConfig.S3Config.UploadPartSize {
  680. return errors.New("S3 upload part size mismatch")
  681. }
  682. if expected.FsConfig.S3Config.UploadConcurrency != actual.FsConfig.S3Config.UploadConcurrency {
  683. return errors.New("S3 upload concurrency mismatch")
  684. }
  685. if expected.FsConfig.S3Config.KeyPrefix != actual.FsConfig.S3Config.KeyPrefix &&
  686. expected.FsConfig.S3Config.KeyPrefix+"/" != actual.FsConfig.S3Config.KeyPrefix {
  687. return errors.New("S3 key prefix mismatch")
  688. }
  689. return nil
  690. }
  691. func compareGCSConfig(expected *dataprovider.User, actual *dataprovider.User) error {
  692. if expected.FsConfig.GCSConfig.Bucket != actual.FsConfig.GCSConfig.Bucket {
  693. return errors.New("GCS bucket mismatch")
  694. }
  695. if expected.FsConfig.GCSConfig.StorageClass != actual.FsConfig.GCSConfig.StorageClass {
  696. return errors.New("GCS storage class mismatch")
  697. }
  698. if expected.FsConfig.GCSConfig.KeyPrefix != actual.FsConfig.GCSConfig.KeyPrefix &&
  699. expected.FsConfig.GCSConfig.KeyPrefix+"/" != actual.FsConfig.GCSConfig.KeyPrefix {
  700. return errors.New("GCS key prefix mismatch")
  701. }
  702. if expected.FsConfig.GCSConfig.AutomaticCredentials != actual.FsConfig.GCSConfig.AutomaticCredentials {
  703. return errors.New("GCS automatic credentials mismatch")
  704. }
  705. return nil
  706. }
  707. func compareSFTPFsConfig(expected *dataprovider.User, actual *dataprovider.User) error {
  708. if expected.FsConfig.SFTPConfig.Endpoint != actual.FsConfig.SFTPConfig.Endpoint {
  709. return errors.New("SFTPFs endpoint mismatch")
  710. }
  711. if expected.FsConfig.SFTPConfig.Username != actual.FsConfig.SFTPConfig.Username {
  712. return errors.New("SFTPFs username mismatch")
  713. }
  714. if err := checkEncryptedSecret(expected.FsConfig.SFTPConfig.Password, actual.FsConfig.SFTPConfig.Password); err != nil {
  715. return fmt.Errorf("SFTPFs password mismatch: %v", err)
  716. }
  717. if err := checkEncryptedSecret(expected.FsConfig.SFTPConfig.PrivateKey, actual.FsConfig.SFTPConfig.PrivateKey); err != nil {
  718. return fmt.Errorf("SFTPFs private key mismatch: %v", err)
  719. }
  720. if expected.FsConfig.SFTPConfig.Prefix != actual.FsConfig.SFTPConfig.Prefix {
  721. if expected.FsConfig.SFTPConfig.Prefix != "" && actual.FsConfig.SFTPConfig.Prefix != "/" {
  722. return errors.New("SFTPFs prefix mismatch")
  723. }
  724. }
  725. if len(expected.FsConfig.SFTPConfig.Fingerprints) != len(actual.FsConfig.SFTPConfig.Fingerprints) {
  726. return errors.New("SFTPFs fingerprints mismatch")
  727. }
  728. for _, value := range actual.FsConfig.SFTPConfig.Fingerprints {
  729. if !utils.IsStringInSlice(value, expected.FsConfig.SFTPConfig.Fingerprints) {
  730. return errors.New("SFTPFs fingerprints mismatch")
  731. }
  732. }
  733. return nil
  734. }
  735. func compareAzBlobConfig(expected *dataprovider.User, actual *dataprovider.User) error {
  736. if expected.FsConfig.AzBlobConfig.Container != actual.FsConfig.AzBlobConfig.Container {
  737. return errors.New("Azure Blob container mismatch")
  738. }
  739. if expected.FsConfig.AzBlobConfig.AccountName != actual.FsConfig.AzBlobConfig.AccountName {
  740. return errors.New("Azure Blob account name mismatch")
  741. }
  742. if err := checkEncryptedSecret(expected.FsConfig.AzBlobConfig.AccountKey, actual.FsConfig.AzBlobConfig.AccountKey); err != nil {
  743. return fmt.Errorf("Azure Blob account key mismatch: %v", err)
  744. }
  745. if expected.FsConfig.AzBlobConfig.Endpoint != actual.FsConfig.AzBlobConfig.Endpoint {
  746. return errors.New("Azure Blob endpoint mismatch")
  747. }
  748. if expected.FsConfig.AzBlobConfig.SASURL != actual.FsConfig.AzBlobConfig.SASURL {
  749. return errors.New("Azure Blob SASL URL mismatch")
  750. }
  751. if expected.FsConfig.AzBlobConfig.UploadPartSize != actual.FsConfig.AzBlobConfig.UploadPartSize {
  752. return errors.New("Azure Blob upload part size mismatch")
  753. }
  754. if expected.FsConfig.AzBlobConfig.UploadConcurrency != actual.FsConfig.AzBlobConfig.UploadConcurrency {
  755. return errors.New("Azure Blob upload concurrency mismatch")
  756. }
  757. if expected.FsConfig.AzBlobConfig.KeyPrefix != actual.FsConfig.AzBlobConfig.KeyPrefix &&
  758. expected.FsConfig.AzBlobConfig.KeyPrefix+"/" != actual.FsConfig.AzBlobConfig.KeyPrefix {
  759. return errors.New("Azure Blob key prefix mismatch")
  760. }
  761. if expected.FsConfig.AzBlobConfig.UseEmulator != actual.FsConfig.AzBlobConfig.UseEmulator {
  762. return errors.New("Azure Blob use emulator mismatch")
  763. }
  764. if expected.FsConfig.AzBlobConfig.AccessTier != actual.FsConfig.AzBlobConfig.AccessTier {
  765. return errors.New("Azure Blob access tier mismatch")
  766. }
  767. return nil
  768. }
  769. func areSecretEquals(expected, actual *kms.Secret) bool {
  770. if expected == nil && actual == nil {
  771. return true
  772. }
  773. if expected != nil && expected.IsEmpty() && actual == nil {
  774. return true
  775. }
  776. if actual != nil && actual.IsEmpty() && expected == nil {
  777. return true
  778. }
  779. return false
  780. }
  781. func checkEncryptedSecret(expected, actual *kms.Secret) error {
  782. if areSecretEquals(expected, actual) {
  783. return nil
  784. }
  785. if expected == nil && actual != nil && !actual.IsEmpty() {
  786. return errors.New("secret mismatch")
  787. }
  788. if actual == nil && expected != nil && !expected.IsEmpty() {
  789. return errors.New("secret mismatch")
  790. }
  791. if expected.IsPlain() && actual.IsEncrypted() {
  792. if actual.GetPayload() == "" {
  793. return errors.New("invalid secret payload")
  794. }
  795. if actual.GetAdditionalData() != "" {
  796. return errors.New("invalid secret additional data")
  797. }
  798. if actual.GetKey() != "" {
  799. return errors.New("invalid secret key")
  800. }
  801. } else {
  802. if expected.GetStatus() != actual.GetStatus() || expected.GetPayload() != actual.GetPayload() {
  803. return errors.New("secret mismatch")
  804. }
  805. }
  806. return nil
  807. }
  808. func compareUserFilters(expected *dataprovider.User, actual *dataprovider.User) error {
  809. if len(expected.Filters.AllowedIP) != len(actual.Filters.AllowedIP) {
  810. return errors.New("AllowedIP mismatch")
  811. }
  812. if len(expected.Filters.DeniedIP) != len(actual.Filters.DeniedIP) {
  813. return errors.New("DeniedIP mismatch")
  814. }
  815. if len(expected.Filters.DeniedLoginMethods) != len(actual.Filters.DeniedLoginMethods) {
  816. return errors.New("Denied login methods mismatch")
  817. }
  818. if len(expected.Filters.DeniedProtocols) != len(actual.Filters.DeniedProtocols) {
  819. return errors.New("Denied protocols mismatch")
  820. }
  821. if expected.Filters.MaxUploadFileSize != actual.Filters.MaxUploadFileSize {
  822. return errors.New("Max upload file size mismatch")
  823. }
  824. for _, IPMask := range expected.Filters.AllowedIP {
  825. if !utils.IsStringInSlice(IPMask, actual.Filters.AllowedIP) {
  826. return errors.New("AllowedIP contents mismatch")
  827. }
  828. }
  829. for _, IPMask := range expected.Filters.DeniedIP {
  830. if !utils.IsStringInSlice(IPMask, actual.Filters.DeniedIP) {
  831. return errors.New("DeniedIP contents mismatch")
  832. }
  833. }
  834. for _, method := range expected.Filters.DeniedLoginMethods {
  835. if !utils.IsStringInSlice(method, actual.Filters.DeniedLoginMethods) {
  836. return errors.New("Denied login methods contents mismatch")
  837. }
  838. }
  839. for _, protocol := range expected.Filters.DeniedProtocols {
  840. if !utils.IsStringInSlice(protocol, actual.Filters.DeniedProtocols) {
  841. return errors.New("Denied protocols contents mismatch")
  842. }
  843. }
  844. if err := compareUserFileExtensionsFilters(expected, actual); err != nil {
  845. return err
  846. }
  847. return compareUserFilePatternsFilters(expected, actual)
  848. }
  849. func checkFilterMatch(expected []string, actual []string) bool {
  850. if len(expected) != len(actual) {
  851. return false
  852. }
  853. for _, e := range expected {
  854. if !utils.IsStringInSlice(strings.ToLower(e), actual) {
  855. return false
  856. }
  857. }
  858. return true
  859. }
  860. func compareUserFilePatternsFilters(expected *dataprovider.User, actual *dataprovider.User) error {
  861. if len(expected.Filters.FilePatterns) != len(actual.Filters.FilePatterns) {
  862. return errors.New("file patterns mismatch")
  863. }
  864. for _, f := range expected.Filters.FilePatterns {
  865. found := false
  866. for _, f1 := range actual.Filters.FilePatterns {
  867. if path.Clean(f.Path) == path.Clean(f1.Path) {
  868. if !checkFilterMatch(f.AllowedPatterns, f1.AllowedPatterns) ||
  869. !checkFilterMatch(f.DeniedPatterns, f1.DeniedPatterns) {
  870. return errors.New("file patterns contents mismatch")
  871. }
  872. found = true
  873. }
  874. }
  875. if !found {
  876. return errors.New("file patterns contents mismatch")
  877. }
  878. }
  879. return nil
  880. }
  881. func compareUserFileExtensionsFilters(expected *dataprovider.User, actual *dataprovider.User) error {
  882. if len(expected.Filters.FileExtensions) != len(actual.Filters.FileExtensions) {
  883. return errors.New("file extensions mismatch")
  884. }
  885. for _, f := range expected.Filters.FileExtensions {
  886. found := false
  887. for _, f1 := range actual.Filters.FileExtensions {
  888. if path.Clean(f.Path) == path.Clean(f1.Path) {
  889. if !checkFilterMatch(f.AllowedExtensions, f1.AllowedExtensions) ||
  890. !checkFilterMatch(f.DeniedExtensions, f1.DeniedExtensions) {
  891. return errors.New("file extensions contents mismatch")
  892. }
  893. found = true
  894. }
  895. }
  896. if !found {
  897. return errors.New("file extensions contents mismatch")
  898. }
  899. }
  900. return nil
  901. }
  902. func compareEqualsUserFields(expected *dataprovider.User, actual *dataprovider.User) error {
  903. if expected.Username != actual.Username {
  904. return errors.New("Username mismatch")
  905. }
  906. if expected.HomeDir != actual.HomeDir {
  907. return errors.New("HomeDir mismatch")
  908. }
  909. if expected.UID != actual.UID {
  910. return errors.New("UID mismatch")
  911. }
  912. if expected.GID != actual.GID {
  913. return errors.New("GID mismatch")
  914. }
  915. if expected.MaxSessions != actual.MaxSessions {
  916. return errors.New("MaxSessions mismatch")
  917. }
  918. if expected.QuotaSize != actual.QuotaSize {
  919. return errors.New("QuotaSize mismatch")
  920. }
  921. if expected.QuotaFiles != actual.QuotaFiles {
  922. return errors.New("QuotaFiles mismatch")
  923. }
  924. if len(expected.Permissions) != len(actual.Permissions) {
  925. return errors.New("Permissions mismatch")
  926. }
  927. if expected.UploadBandwidth != actual.UploadBandwidth {
  928. return errors.New("UploadBandwidth mismatch")
  929. }
  930. if expected.DownloadBandwidth != actual.DownloadBandwidth {
  931. return errors.New("DownloadBandwidth mismatch")
  932. }
  933. if expected.Status != actual.Status {
  934. return errors.New("Status mismatch")
  935. }
  936. if expected.ExpirationDate != actual.ExpirationDate {
  937. return errors.New("ExpirationDate mismatch")
  938. }
  939. if expected.AdditionalInfo != actual.AdditionalInfo {
  940. return errors.New("AdditionalInfo mismatch")
  941. }
  942. return nil
  943. }
  944. func addLimitAndOffsetQueryParams(rawurl string, limit, offset int64) (*url.URL, error) {
  945. url, err := url.Parse(rawurl)
  946. if err != nil {
  947. return nil, err
  948. }
  949. q := url.Query()
  950. if limit > 0 {
  951. q.Add("limit", strconv.FormatInt(limit, 10))
  952. }
  953. if offset > 0 {
  954. q.Add("offset", strconv.FormatInt(offset, 10))
  955. }
  956. url.RawQuery = q.Encode()
  957. return url, err
  958. }
  959. func addModeQueryParam(rawurl, mode string) (*url.URL, error) {
  960. url, err := url.Parse(rawurl)
  961. if err != nil {
  962. return nil, err
  963. }
  964. q := url.Query()
  965. if len(mode) > 0 {
  966. q.Add("mode", mode)
  967. }
  968. url.RawQuery = q.Encode()
  969. return url, err
  970. }
  971. func addDisconnectQueryParam(rawurl, disconnect string) (*url.URL, error) {
  972. url, err := url.Parse(rawurl)
  973. if err != nil {
  974. return nil, err
  975. }
  976. q := url.Query()
  977. if len(disconnect) > 0 {
  978. q.Add("disconnect", disconnect)
  979. }
  980. url.RawQuery = q.Encode()
  981. return url, err
  982. }