httpdtest.go 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615
  1. // Package httpdtest provides utilities for testing the exposed REST API.
  2. package httpdtest
  3. import (
  4. "bytes"
  5. "encoding/hex"
  6. "encoding/json"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "net/http"
  11. "net/url"
  12. "path"
  13. "strconv"
  14. "strings"
  15. "github.com/go-chi/render"
  16. "github.com/drakkan/sftpgo/v2/common"
  17. "github.com/drakkan/sftpgo/v2/dataprovider"
  18. "github.com/drakkan/sftpgo/v2/httpclient"
  19. "github.com/drakkan/sftpgo/v2/httpd"
  20. "github.com/drakkan/sftpgo/v2/kms"
  21. "github.com/drakkan/sftpgo/v2/util"
  22. "github.com/drakkan/sftpgo/v2/version"
  23. "github.com/drakkan/sftpgo/v2/vfs"
  24. )
  25. const (
  26. tokenPath = "/api/v2/token"
  27. activeConnectionsPath = "/api/v2/connections"
  28. quotasBasePath = "/api/v2/quotas"
  29. quotaScanPath = "/api/v2/quotas/users/scans"
  30. quotaScanVFolderPath = "/api/v2/quotas/folders/scans"
  31. userPath = "/api/v2/users"
  32. versionPath = "/api/v2/version"
  33. folderPath = "/api/v2/folders"
  34. serverStatusPath = "/api/v2/status"
  35. dumpDataPath = "/api/v2/dumpdata"
  36. loadDataPath = "/api/v2/loaddata"
  37. defenderHosts = "/api/v2/defender/hosts"
  38. defenderBanTime = "/api/v2/defender/bantime"
  39. defenderUnban = "/api/v2/defender/unban"
  40. defenderScore = "/api/v2/defender/score"
  41. adminPath = "/api/v2/admins"
  42. adminPwdPath = "/api/v2/admin/changepwd"
  43. apiKeysPath = "/api/v2/apikeys"
  44. retentionBasePath = "/api/v2/retention/users"
  45. retentionChecksPath = "/api/v2/retention/users/checks"
  46. )
  47. const (
  48. defaultTokenAuthUser = "admin"
  49. defaultTokenAuthPass = "password"
  50. )
  51. var (
  52. httpBaseURL = "http://127.0.0.1:8080"
  53. jwtToken = ""
  54. )
  55. // SetBaseURL sets the base url to use for HTTP requests.
  56. // Default URL is "http://127.0.0.1:8080"
  57. func SetBaseURL(url string) {
  58. httpBaseURL = url
  59. }
  60. // SetJWTToken sets the JWT token to use
  61. func SetJWTToken(token string) {
  62. jwtToken = token
  63. }
  64. func sendHTTPRequest(method, url string, body io.Reader, contentType, token string) (*http.Response, error) {
  65. req, err := http.NewRequest(method, url, body)
  66. if err != nil {
  67. return nil, err
  68. }
  69. if contentType != "" {
  70. req.Header.Set("Content-Type", "application/json")
  71. }
  72. if token != "" {
  73. req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", token))
  74. }
  75. return httpclient.GetHTTPClient().Do(req)
  76. }
  77. func buildURLRelativeToBase(paths ...string) string {
  78. // we need to use path.Join and not filepath.Join
  79. // since filepath.Join will use backslash separator on Windows
  80. p := path.Join(paths...)
  81. return fmt.Sprintf("%s/%s", strings.TrimRight(httpBaseURL, "/"), strings.TrimLeft(p, "/"))
  82. }
  83. // GetToken tries to return a JWT token
  84. func GetToken(username, password string) (string, map[string]interface{}, error) {
  85. req, err := http.NewRequest(http.MethodGet, buildURLRelativeToBase(tokenPath), nil)
  86. if err != nil {
  87. return "", nil, err
  88. }
  89. req.SetBasicAuth(username, password)
  90. resp, err := httpclient.GetHTTPClient().Do(req)
  91. if err != nil {
  92. return "", nil, err
  93. }
  94. defer resp.Body.Close()
  95. err = checkResponse(resp.StatusCode, http.StatusOK)
  96. if err != nil {
  97. return "", nil, err
  98. }
  99. responseHolder := make(map[string]interface{})
  100. err = render.DecodeJSON(resp.Body, &responseHolder)
  101. if err != nil {
  102. return "", nil, err
  103. }
  104. return responseHolder["access_token"].(string), responseHolder, nil
  105. }
  106. func getDefaultToken() string {
  107. if jwtToken != "" {
  108. return jwtToken
  109. }
  110. token, _, err := GetToken(defaultTokenAuthUser, defaultTokenAuthPass)
  111. if err != nil {
  112. return ""
  113. }
  114. return token
  115. }
  116. // AddUser adds a new user and checks the received HTTP Status code against expectedStatusCode.
  117. func AddUser(user dataprovider.User, expectedStatusCode int) (dataprovider.User, []byte, error) {
  118. var newUser dataprovider.User
  119. var body []byte
  120. userAsJSON, _ := json.Marshal(user)
  121. resp, err := sendHTTPRequest(http.MethodPost, buildURLRelativeToBase(userPath), bytes.NewBuffer(userAsJSON),
  122. "application/json", getDefaultToken())
  123. if err != nil {
  124. return newUser, body, err
  125. }
  126. defer resp.Body.Close()
  127. err = checkResponse(resp.StatusCode, expectedStatusCode)
  128. if expectedStatusCode != http.StatusCreated {
  129. body, _ = getResponseBody(resp)
  130. return newUser, body, err
  131. }
  132. if err == nil {
  133. err = render.DecodeJSON(resp.Body, &newUser)
  134. } else {
  135. body, _ = getResponseBody(resp)
  136. }
  137. if err == nil {
  138. err = checkUser(&user, &newUser)
  139. }
  140. return newUser, body, err
  141. }
  142. // UpdateUserWithJSON update a user using the provided JSON as POST body
  143. func UpdateUserWithJSON(user dataprovider.User, expectedStatusCode int, disconnect string, userAsJSON []byte) (dataprovider.User, []byte, error) {
  144. var newUser dataprovider.User
  145. var body []byte
  146. url, err := addDisconnectQueryParam(buildURLRelativeToBase(userPath, url.PathEscape(user.Username)), disconnect)
  147. if err != nil {
  148. return user, body, err
  149. }
  150. resp, err := sendHTTPRequest(http.MethodPut, url.String(), bytes.NewBuffer(userAsJSON), "application/json",
  151. getDefaultToken())
  152. if err != nil {
  153. return user, body, err
  154. }
  155. defer resp.Body.Close()
  156. body, _ = getResponseBody(resp)
  157. err = checkResponse(resp.StatusCode, expectedStatusCode)
  158. if expectedStatusCode != http.StatusOK {
  159. return newUser, body, err
  160. }
  161. if err == nil {
  162. newUser, body, err = GetUserByUsername(user.Username, expectedStatusCode)
  163. }
  164. if err == nil {
  165. err = checkUser(&user, &newUser)
  166. }
  167. return newUser, body, err
  168. }
  169. // UpdateUser updates an existing user and checks the received HTTP Status code against expectedStatusCode.
  170. func UpdateUser(user dataprovider.User, expectedStatusCode int, disconnect string) (dataprovider.User, []byte, error) {
  171. userAsJSON, _ := json.Marshal(user)
  172. return UpdateUserWithJSON(user, expectedStatusCode, disconnect, userAsJSON)
  173. }
  174. // RemoveUser removes an existing user and checks the received HTTP Status code against expectedStatusCode.
  175. func RemoveUser(user dataprovider.User, expectedStatusCode int) ([]byte, error) {
  176. var body []byte
  177. resp, err := sendHTTPRequest(http.MethodDelete, buildURLRelativeToBase(userPath, url.PathEscape(user.Username)),
  178. nil, "", getDefaultToken())
  179. if err != nil {
  180. return body, err
  181. }
  182. defer resp.Body.Close()
  183. body, _ = getResponseBody(resp)
  184. return body, checkResponse(resp.StatusCode, expectedStatusCode)
  185. }
  186. // GetUserByUsername gets a user by username and checks the received HTTP Status code against expectedStatusCode.
  187. func GetUserByUsername(username string, expectedStatusCode int) (dataprovider.User, []byte, error) {
  188. var user dataprovider.User
  189. var body []byte
  190. resp, err := sendHTTPRequest(http.MethodGet, buildURLRelativeToBase(userPath, url.PathEscape(username)),
  191. nil, "", getDefaultToken())
  192. if err != nil {
  193. return user, body, err
  194. }
  195. defer resp.Body.Close()
  196. err = checkResponse(resp.StatusCode, expectedStatusCode)
  197. if err == nil && expectedStatusCode == http.StatusOK {
  198. err = render.DecodeJSON(resp.Body, &user)
  199. } else {
  200. body, _ = getResponseBody(resp)
  201. }
  202. return user, body, err
  203. }
  204. // GetUsers returns a list of users and checks the received HTTP Status code against expectedStatusCode.
  205. // The number of results can be limited specifying a limit.
  206. // Some results can be skipped specifying an offset.
  207. func GetUsers(limit, offset int64, expectedStatusCode int) ([]dataprovider.User, []byte, error) {
  208. var users []dataprovider.User
  209. var body []byte
  210. url, err := addLimitAndOffsetQueryParams(buildURLRelativeToBase(userPath), limit, offset)
  211. if err != nil {
  212. return users, body, err
  213. }
  214. resp, err := sendHTTPRequest(http.MethodGet, url.String(), nil, "", getDefaultToken())
  215. if err != nil {
  216. return users, body, err
  217. }
  218. defer resp.Body.Close()
  219. err = checkResponse(resp.StatusCode, expectedStatusCode)
  220. if err == nil && expectedStatusCode == http.StatusOK {
  221. err = render.DecodeJSON(resp.Body, &users)
  222. } else {
  223. body, _ = getResponseBody(resp)
  224. }
  225. return users, body, err
  226. }
  227. // AddAdmin adds a new admin and checks the received HTTP Status code against expectedStatusCode.
  228. func AddAdmin(admin dataprovider.Admin, expectedStatusCode int) (dataprovider.Admin, []byte, error) {
  229. var newAdmin dataprovider.Admin
  230. var body []byte
  231. asJSON, _ := json.Marshal(admin)
  232. resp, err := sendHTTPRequest(http.MethodPost, buildURLRelativeToBase(adminPath), bytes.NewBuffer(asJSON),
  233. "application/json", getDefaultToken())
  234. if err != nil {
  235. return newAdmin, body, err
  236. }
  237. defer resp.Body.Close()
  238. err = checkResponse(resp.StatusCode, expectedStatusCode)
  239. if expectedStatusCode != http.StatusCreated {
  240. body, _ = getResponseBody(resp)
  241. return newAdmin, body, err
  242. }
  243. if err == nil {
  244. err = render.DecodeJSON(resp.Body, &newAdmin)
  245. } else {
  246. body, _ = getResponseBody(resp)
  247. }
  248. if err == nil {
  249. err = checkAdmin(&admin, &newAdmin)
  250. }
  251. return newAdmin, body, err
  252. }
  253. // UpdateAdmin updates an existing admin and checks the received HTTP Status code against expectedStatusCode
  254. func UpdateAdmin(admin dataprovider.Admin, expectedStatusCode int) (dataprovider.Admin, []byte, error) {
  255. var newAdmin dataprovider.Admin
  256. var body []byte
  257. asJSON, _ := json.Marshal(admin)
  258. resp, err := sendHTTPRequest(http.MethodPut, buildURLRelativeToBase(adminPath, url.PathEscape(admin.Username)),
  259. bytes.NewBuffer(asJSON), "application/json", getDefaultToken())
  260. if err != nil {
  261. return newAdmin, body, err
  262. }
  263. defer resp.Body.Close()
  264. body, _ = getResponseBody(resp)
  265. err = checkResponse(resp.StatusCode, expectedStatusCode)
  266. if expectedStatusCode != http.StatusOK {
  267. return newAdmin, body, err
  268. }
  269. if err == nil {
  270. newAdmin, body, err = GetAdminByUsername(admin.Username, expectedStatusCode)
  271. }
  272. if err == nil {
  273. err = checkAdmin(&admin, &newAdmin)
  274. }
  275. return newAdmin, body, err
  276. }
  277. // RemoveAdmin removes an existing admin and checks the received HTTP Status code against expectedStatusCode.
  278. func RemoveAdmin(admin dataprovider.Admin, expectedStatusCode int) ([]byte, error) {
  279. var body []byte
  280. resp, err := sendHTTPRequest(http.MethodDelete, buildURLRelativeToBase(adminPath, url.PathEscape(admin.Username)),
  281. nil, "", getDefaultToken())
  282. if err != nil {
  283. return body, err
  284. }
  285. defer resp.Body.Close()
  286. body, _ = getResponseBody(resp)
  287. return body, checkResponse(resp.StatusCode, expectedStatusCode)
  288. }
  289. // GetAdminByUsername gets an admin by username and checks the received HTTP Status code against expectedStatusCode.
  290. func GetAdminByUsername(username string, expectedStatusCode int) (dataprovider.Admin, []byte, error) {
  291. var admin dataprovider.Admin
  292. var body []byte
  293. resp, err := sendHTTPRequest(http.MethodGet, buildURLRelativeToBase(adminPath, url.PathEscape(username)),
  294. nil, "", getDefaultToken())
  295. if err != nil {
  296. return admin, body, err
  297. }
  298. defer resp.Body.Close()
  299. err = checkResponse(resp.StatusCode, expectedStatusCode)
  300. if err == nil && expectedStatusCode == http.StatusOK {
  301. err = render.DecodeJSON(resp.Body, &admin)
  302. } else {
  303. body, _ = getResponseBody(resp)
  304. }
  305. return admin, body, err
  306. }
  307. // GetAdmins returns a list of admins and checks the received HTTP Status code against expectedStatusCode.
  308. // The number of results can be limited specifying a limit.
  309. // Some results can be skipped specifying an offset.
  310. func GetAdmins(limit, offset int64, expectedStatusCode int) ([]dataprovider.Admin, []byte, error) {
  311. var admins []dataprovider.Admin
  312. var body []byte
  313. url, err := addLimitAndOffsetQueryParams(buildURLRelativeToBase(adminPath), limit, offset)
  314. if err != nil {
  315. return admins, body, err
  316. }
  317. resp, err := sendHTTPRequest(http.MethodGet, url.String(), nil, "", getDefaultToken())
  318. if err != nil {
  319. return admins, body, err
  320. }
  321. defer resp.Body.Close()
  322. err = checkResponse(resp.StatusCode, expectedStatusCode)
  323. if err == nil && expectedStatusCode == http.StatusOK {
  324. err = render.DecodeJSON(resp.Body, &admins)
  325. } else {
  326. body, _ = getResponseBody(resp)
  327. }
  328. return admins, body, err
  329. }
  330. // ChangeAdminPassword changes the password for an existing admin
  331. func ChangeAdminPassword(currentPassword, newPassword string, expectedStatusCode int) ([]byte, error) {
  332. var body []byte
  333. pwdChange := make(map[string]string)
  334. pwdChange["current_password"] = currentPassword
  335. pwdChange["new_password"] = newPassword
  336. asJSON, _ := json.Marshal(&pwdChange)
  337. resp, err := sendHTTPRequest(http.MethodPut, buildURLRelativeToBase(adminPwdPath),
  338. bytes.NewBuffer(asJSON), "application/json", getDefaultToken())
  339. if err != nil {
  340. return body, err
  341. }
  342. defer resp.Body.Close()
  343. err = checkResponse(resp.StatusCode, expectedStatusCode)
  344. body, _ = getResponseBody(resp)
  345. return body, err
  346. }
  347. // GetAPIKeys returns a list of API keys and checks the received HTTP Status code against expectedStatusCode.
  348. // The number of results can be limited specifying a limit.
  349. // Some results can be skipped specifying an offset.
  350. func GetAPIKeys(limit, offset int64, expectedStatusCode int) ([]dataprovider.APIKey, []byte, error) {
  351. var apiKeys []dataprovider.APIKey
  352. var body []byte
  353. url, err := addLimitAndOffsetQueryParams(buildURLRelativeToBase(apiKeysPath), limit, offset)
  354. if err != nil {
  355. return apiKeys, body, err
  356. }
  357. resp, err := sendHTTPRequest(http.MethodGet, url.String(), nil, "", getDefaultToken())
  358. if err != nil {
  359. return apiKeys, body, err
  360. }
  361. defer resp.Body.Close()
  362. err = checkResponse(resp.StatusCode, expectedStatusCode)
  363. if err == nil && expectedStatusCode == http.StatusOK {
  364. err = render.DecodeJSON(resp.Body, &apiKeys)
  365. } else {
  366. body, _ = getResponseBody(resp)
  367. }
  368. return apiKeys, body, err
  369. }
  370. // AddAPIKey adds a new API key and checks the received HTTP Status code against expectedStatusCode.
  371. func AddAPIKey(apiKey dataprovider.APIKey, expectedStatusCode int) (dataprovider.APIKey, []byte, error) {
  372. var newAPIKey dataprovider.APIKey
  373. var body []byte
  374. asJSON, _ := json.Marshal(apiKey)
  375. resp, err := sendHTTPRequest(http.MethodPost, buildURLRelativeToBase(apiKeysPath), bytes.NewBuffer(asJSON),
  376. "application/json", getDefaultToken())
  377. if err != nil {
  378. return newAPIKey, body, err
  379. }
  380. defer resp.Body.Close()
  381. err = checkResponse(resp.StatusCode, expectedStatusCode)
  382. if expectedStatusCode != http.StatusCreated {
  383. body, _ = getResponseBody(resp)
  384. return newAPIKey, body, err
  385. }
  386. if err != nil {
  387. body, _ = getResponseBody(resp)
  388. return newAPIKey, body, err
  389. }
  390. response := make(map[string]string)
  391. err = render.DecodeJSON(resp.Body, &response)
  392. if err == nil {
  393. newAPIKey, body, err = GetAPIKeyByID(resp.Header.Get("X-Object-ID"), http.StatusOK)
  394. }
  395. if err == nil {
  396. err = checkAPIKey(&apiKey, &newAPIKey)
  397. }
  398. newAPIKey.Key = response["key"]
  399. return newAPIKey, body, err
  400. }
  401. // UpdateAPIKey updates an existing API key and checks the received HTTP Status code against expectedStatusCode
  402. func UpdateAPIKey(apiKey dataprovider.APIKey, expectedStatusCode int) (dataprovider.APIKey, []byte, error) {
  403. var newAPIKey dataprovider.APIKey
  404. var body []byte
  405. asJSON, _ := json.Marshal(apiKey)
  406. resp, err := sendHTTPRequest(http.MethodPut, buildURLRelativeToBase(apiKeysPath, url.PathEscape(apiKey.KeyID)),
  407. bytes.NewBuffer(asJSON), "application/json", getDefaultToken())
  408. if err != nil {
  409. return newAPIKey, body, err
  410. }
  411. defer resp.Body.Close()
  412. body, _ = getResponseBody(resp)
  413. err = checkResponse(resp.StatusCode, expectedStatusCode)
  414. if expectedStatusCode != http.StatusOK {
  415. return newAPIKey, body, err
  416. }
  417. if err == nil {
  418. newAPIKey, body, err = GetAPIKeyByID(apiKey.KeyID, expectedStatusCode)
  419. }
  420. if err == nil {
  421. err = checkAPIKey(&apiKey, &newAPIKey)
  422. }
  423. return newAPIKey, body, err
  424. }
  425. // RemoveAPIKey removes an existing API key and checks the received HTTP Status code against expectedStatusCode.
  426. func RemoveAPIKey(apiKey dataprovider.APIKey, expectedStatusCode int) ([]byte, error) {
  427. var body []byte
  428. resp, err := sendHTTPRequest(http.MethodDelete, buildURLRelativeToBase(apiKeysPath, url.PathEscape(apiKey.KeyID)),
  429. nil, "", getDefaultToken())
  430. if err != nil {
  431. return body, err
  432. }
  433. defer resp.Body.Close()
  434. body, _ = getResponseBody(resp)
  435. return body, checkResponse(resp.StatusCode, expectedStatusCode)
  436. }
  437. // GetAPIKeyByID gets a API key by ID and checks the received HTTP Status code against expectedStatusCode.
  438. func GetAPIKeyByID(keyID string, expectedStatusCode int) (dataprovider.APIKey, []byte, error) {
  439. var apiKey dataprovider.APIKey
  440. var body []byte
  441. resp, err := sendHTTPRequest(http.MethodGet, buildURLRelativeToBase(apiKeysPath, url.PathEscape(keyID)),
  442. nil, "", getDefaultToken())
  443. if err != nil {
  444. return apiKey, body, err
  445. }
  446. defer resp.Body.Close()
  447. err = checkResponse(resp.StatusCode, expectedStatusCode)
  448. if err == nil && expectedStatusCode == http.StatusOK {
  449. err = render.DecodeJSON(resp.Body, &apiKey)
  450. } else {
  451. body, _ = getResponseBody(resp)
  452. }
  453. return apiKey, body, err
  454. }
  455. // GetQuotaScans gets active quota scans for users and checks the received HTTP Status code against expectedStatusCode.
  456. func GetQuotaScans(expectedStatusCode int) ([]common.ActiveQuotaScan, []byte, error) {
  457. var quotaScans []common.ActiveQuotaScan
  458. var body []byte
  459. resp, err := sendHTTPRequest(http.MethodGet, buildURLRelativeToBase(quotaScanPath), nil, "", getDefaultToken())
  460. if err != nil {
  461. return quotaScans, body, err
  462. }
  463. defer resp.Body.Close()
  464. err = checkResponse(resp.StatusCode, expectedStatusCode)
  465. if err == nil && expectedStatusCode == http.StatusOK {
  466. err = render.DecodeJSON(resp.Body, &quotaScans)
  467. } else {
  468. body, _ = getResponseBody(resp)
  469. }
  470. return quotaScans, body, err
  471. }
  472. // StartQuotaScan starts a new quota scan for the given user and checks the received HTTP Status code against expectedStatusCode.
  473. func StartQuotaScan(user dataprovider.User, expectedStatusCode int) ([]byte, error) {
  474. var body []byte
  475. resp, err := sendHTTPRequest(http.MethodPost, buildURLRelativeToBase(quotasBasePath, "users", user.Username, "scan"),
  476. nil, "", getDefaultToken())
  477. if err != nil {
  478. return body, err
  479. }
  480. defer resp.Body.Close()
  481. body, _ = getResponseBody(resp)
  482. return body, checkResponse(resp.StatusCode, expectedStatusCode)
  483. }
  484. // UpdateQuotaUsage updates the user used quota limits and checks the received HTTP Status code against expectedStatusCode.
  485. func UpdateQuotaUsage(user dataprovider.User, mode string, expectedStatusCode int) ([]byte, error) {
  486. var body []byte
  487. userAsJSON, _ := json.Marshal(user)
  488. url, err := addModeQueryParam(buildURLRelativeToBase(quotasBasePath, "users", user.Username, "usage"), mode)
  489. if err != nil {
  490. return body, err
  491. }
  492. resp, err := sendHTTPRequest(http.MethodPut, url.String(), bytes.NewBuffer(userAsJSON), "application/json",
  493. getDefaultToken())
  494. if err != nil {
  495. return body, err
  496. }
  497. defer resp.Body.Close()
  498. body, _ = getResponseBody(resp)
  499. return body, checkResponse(resp.StatusCode, expectedStatusCode)
  500. }
  501. // GetRetentionChecks returns the active retention checks
  502. func GetRetentionChecks(expectedStatusCode int) ([]common.ActiveRetentionChecks, []byte, error) {
  503. var checks []common.ActiveRetentionChecks
  504. var body []byte
  505. resp, err := sendHTTPRequest(http.MethodGet, buildURLRelativeToBase(retentionChecksPath), nil, "", getDefaultToken())
  506. if err != nil {
  507. return checks, body, err
  508. }
  509. defer resp.Body.Close()
  510. err = checkResponse(resp.StatusCode, expectedStatusCode)
  511. if err == nil && expectedStatusCode == http.StatusOK {
  512. err = render.DecodeJSON(resp.Body, &checks)
  513. } else {
  514. body, _ = getResponseBody(resp)
  515. }
  516. return checks, body, err
  517. }
  518. // StartRetentionCheck starts a new retention check
  519. func StartRetentionCheck(username string, retention []common.FolderRetention, expectedStatusCode int) ([]byte, error) {
  520. var body []byte
  521. asJSON, _ := json.Marshal(retention)
  522. resp, err := sendHTTPRequest(http.MethodPost, buildURLRelativeToBase(retentionBasePath, username, "check"),
  523. bytes.NewBuffer(asJSON), "application/json", getDefaultToken())
  524. if err != nil {
  525. return body, err
  526. }
  527. defer resp.Body.Close()
  528. body, _ = getResponseBody(resp)
  529. return body, checkResponse(resp.StatusCode, expectedStatusCode)
  530. }
  531. // GetConnections returns status and stats for active SFTP/SCP connections
  532. func GetConnections(expectedStatusCode int) ([]common.ConnectionStatus, []byte, error) {
  533. var connections []common.ConnectionStatus
  534. var body []byte
  535. resp, err := sendHTTPRequest(http.MethodGet, buildURLRelativeToBase(activeConnectionsPath), nil, "", getDefaultToken())
  536. if err != nil {
  537. return connections, body, err
  538. }
  539. defer resp.Body.Close()
  540. err = checkResponse(resp.StatusCode, expectedStatusCode)
  541. if err == nil && expectedStatusCode == http.StatusOK {
  542. err = render.DecodeJSON(resp.Body, &connections)
  543. } else {
  544. body, _ = getResponseBody(resp)
  545. }
  546. return connections, body, err
  547. }
  548. // CloseConnection closes an active connection identified by connectionID
  549. func CloseConnection(connectionID string, expectedStatusCode int) ([]byte, error) {
  550. var body []byte
  551. resp, err := sendHTTPRequest(http.MethodDelete, buildURLRelativeToBase(activeConnectionsPath, connectionID),
  552. nil, "", getDefaultToken())
  553. if err != nil {
  554. return body, err
  555. }
  556. defer resp.Body.Close()
  557. err = checkResponse(resp.StatusCode, expectedStatusCode)
  558. body, _ = getResponseBody(resp)
  559. return body, err
  560. }
  561. // AddFolder adds a new folder and checks the received HTTP Status code against expectedStatusCode
  562. func AddFolder(folder vfs.BaseVirtualFolder, expectedStatusCode int) (vfs.BaseVirtualFolder, []byte, error) {
  563. var newFolder vfs.BaseVirtualFolder
  564. var body []byte
  565. folderAsJSON, _ := json.Marshal(folder)
  566. resp, err := sendHTTPRequest(http.MethodPost, buildURLRelativeToBase(folderPath), bytes.NewBuffer(folderAsJSON),
  567. "application/json", getDefaultToken())
  568. if err != nil {
  569. return newFolder, body, err
  570. }
  571. defer resp.Body.Close()
  572. err = checkResponse(resp.StatusCode, expectedStatusCode)
  573. if expectedStatusCode != http.StatusCreated {
  574. body, _ = getResponseBody(resp)
  575. return newFolder, body, err
  576. }
  577. if err == nil {
  578. err = render.DecodeJSON(resp.Body, &newFolder)
  579. } else {
  580. body, _ = getResponseBody(resp)
  581. }
  582. if err == nil {
  583. err = checkFolder(&folder, &newFolder)
  584. }
  585. return newFolder, body, err
  586. }
  587. // UpdateFolder updates an existing folder and checks the received HTTP Status code against expectedStatusCode.
  588. func UpdateFolder(folder vfs.BaseVirtualFolder, expectedStatusCode int) (vfs.BaseVirtualFolder, []byte, error) {
  589. var updatedFolder vfs.BaseVirtualFolder
  590. var body []byte
  591. folderAsJSON, _ := json.Marshal(folder)
  592. resp, err := sendHTTPRequest(http.MethodPut, buildURLRelativeToBase(folderPath, url.PathEscape(folder.Name)),
  593. bytes.NewBuffer(folderAsJSON), "application/json", getDefaultToken())
  594. if err != nil {
  595. return updatedFolder, body, err
  596. }
  597. defer resp.Body.Close()
  598. body, _ = getResponseBody(resp)
  599. err = checkResponse(resp.StatusCode, expectedStatusCode)
  600. if expectedStatusCode != http.StatusOK {
  601. return updatedFolder, body, err
  602. }
  603. if err == nil {
  604. updatedFolder, body, err = GetFolderByName(folder.Name, expectedStatusCode)
  605. }
  606. if err == nil {
  607. err = checkFolder(&folder, &updatedFolder)
  608. }
  609. return updatedFolder, body, err
  610. }
  611. // RemoveFolder removes an existing user and checks the received HTTP Status code against expectedStatusCode.
  612. func RemoveFolder(folder vfs.BaseVirtualFolder, expectedStatusCode int) ([]byte, error) {
  613. var body []byte
  614. resp, err := sendHTTPRequest(http.MethodDelete, buildURLRelativeToBase(folderPath, url.PathEscape(folder.Name)),
  615. nil, "", getDefaultToken())
  616. if err != nil {
  617. return body, err
  618. }
  619. defer resp.Body.Close()
  620. body, _ = getResponseBody(resp)
  621. return body, checkResponse(resp.StatusCode, expectedStatusCode)
  622. }
  623. // GetFolderByName gets a folder by name and checks the received HTTP Status code against expectedStatusCode.
  624. func GetFolderByName(name string, expectedStatusCode int) (vfs.BaseVirtualFolder, []byte, error) {
  625. var folder vfs.BaseVirtualFolder
  626. var body []byte
  627. resp, err := sendHTTPRequest(http.MethodGet, buildURLRelativeToBase(folderPath, url.PathEscape(name)),
  628. nil, "", getDefaultToken())
  629. if err != nil {
  630. return folder, body, err
  631. }
  632. defer resp.Body.Close()
  633. err = checkResponse(resp.StatusCode, expectedStatusCode)
  634. if err == nil && expectedStatusCode == http.StatusOK {
  635. err = render.DecodeJSON(resp.Body, &folder)
  636. } else {
  637. body, _ = getResponseBody(resp)
  638. }
  639. return folder, body, err
  640. }
  641. // GetFolders returns a list of folders and checks the received HTTP Status code against expectedStatusCode.
  642. // The number of results can be limited specifying a limit.
  643. // Some results can be skipped specifying an offset.
  644. // The results can be filtered specifying a folder path, the folder path filter is an exact match
  645. func GetFolders(limit int64, offset int64, expectedStatusCode int) ([]vfs.BaseVirtualFolder, []byte, error) {
  646. var folders []vfs.BaseVirtualFolder
  647. var body []byte
  648. url, err := addLimitAndOffsetQueryParams(buildURLRelativeToBase(folderPath), limit, offset)
  649. if err != nil {
  650. return folders, body, err
  651. }
  652. resp, err := sendHTTPRequest(http.MethodGet, url.String(), nil, "", getDefaultToken())
  653. if err != nil {
  654. return folders, body, err
  655. }
  656. defer resp.Body.Close()
  657. err = checkResponse(resp.StatusCode, expectedStatusCode)
  658. if err == nil && expectedStatusCode == http.StatusOK {
  659. err = render.DecodeJSON(resp.Body, &folders)
  660. } else {
  661. body, _ = getResponseBody(resp)
  662. }
  663. return folders, body, err
  664. }
  665. // GetFoldersQuotaScans gets active quota scans for folders and checks the received HTTP Status code against expectedStatusCode.
  666. func GetFoldersQuotaScans(expectedStatusCode int) ([]common.ActiveVirtualFolderQuotaScan, []byte, error) {
  667. var quotaScans []common.ActiveVirtualFolderQuotaScan
  668. var body []byte
  669. resp, err := sendHTTPRequest(http.MethodGet, buildURLRelativeToBase(quotaScanVFolderPath), nil, "", getDefaultToken())
  670. if err != nil {
  671. return quotaScans, body, err
  672. }
  673. defer resp.Body.Close()
  674. err = checkResponse(resp.StatusCode, expectedStatusCode)
  675. if err == nil && expectedStatusCode == http.StatusOK {
  676. err = render.DecodeJSON(resp.Body, &quotaScans)
  677. } else {
  678. body, _ = getResponseBody(resp)
  679. }
  680. return quotaScans, body, err
  681. }
  682. // StartFolderQuotaScan start a new quota scan for the given folder and checks the received HTTP Status code against expectedStatusCode.
  683. func StartFolderQuotaScan(folder vfs.BaseVirtualFolder, expectedStatusCode int) ([]byte, error) {
  684. var body []byte
  685. resp, err := sendHTTPRequest(http.MethodPost, buildURLRelativeToBase(quotasBasePath, "folders", folder.Name, "scan"),
  686. nil, "", getDefaultToken())
  687. if err != nil {
  688. return body, err
  689. }
  690. defer resp.Body.Close()
  691. body, _ = getResponseBody(resp)
  692. return body, checkResponse(resp.StatusCode, expectedStatusCode)
  693. }
  694. // UpdateFolderQuotaUsage updates the folder used quota limits and checks the received HTTP Status code against expectedStatusCode.
  695. func UpdateFolderQuotaUsage(folder vfs.BaseVirtualFolder, mode string, expectedStatusCode int) ([]byte, error) {
  696. var body []byte
  697. folderAsJSON, _ := json.Marshal(folder)
  698. url, err := addModeQueryParam(buildURLRelativeToBase(quotasBasePath, "folders", folder.Name, "usage"), mode)
  699. if err != nil {
  700. return body, err
  701. }
  702. resp, err := sendHTTPRequest(http.MethodPut, url.String(), bytes.NewBuffer(folderAsJSON), "", getDefaultToken())
  703. if err != nil {
  704. return body, err
  705. }
  706. defer resp.Body.Close()
  707. body, _ = getResponseBody(resp)
  708. return body, checkResponse(resp.StatusCode, expectedStatusCode)
  709. }
  710. // GetVersion returns version details
  711. func GetVersion(expectedStatusCode int) (version.Info, []byte, error) {
  712. var appVersion version.Info
  713. var body []byte
  714. resp, err := sendHTTPRequest(http.MethodGet, buildURLRelativeToBase(versionPath), nil, "", getDefaultToken())
  715. if err != nil {
  716. return appVersion, body, err
  717. }
  718. defer resp.Body.Close()
  719. err = checkResponse(resp.StatusCode, expectedStatusCode)
  720. if err == nil && expectedStatusCode == http.StatusOK {
  721. err = render.DecodeJSON(resp.Body, &appVersion)
  722. } else {
  723. body, _ = getResponseBody(resp)
  724. }
  725. return appVersion, body, err
  726. }
  727. // GetStatus returns the server status
  728. func GetStatus(expectedStatusCode int) (httpd.ServicesStatus, []byte, error) {
  729. var response httpd.ServicesStatus
  730. var body []byte
  731. resp, err := sendHTTPRequest(http.MethodGet, buildURLRelativeToBase(serverStatusPath), nil, "", getDefaultToken())
  732. if err != nil {
  733. return response, body, err
  734. }
  735. defer resp.Body.Close()
  736. err = checkResponse(resp.StatusCode, expectedStatusCode)
  737. if err == nil && (expectedStatusCode == http.StatusOK) {
  738. err = render.DecodeJSON(resp.Body, &response)
  739. } else {
  740. body, _ = getResponseBody(resp)
  741. }
  742. return response, body, err
  743. }
  744. // GetDefenderHosts returns hosts that are banned or for which some violations have been detected
  745. func GetDefenderHosts(expectedStatusCode int) ([]common.DefenderEntry, []byte, error) {
  746. var response []common.DefenderEntry
  747. var body []byte
  748. url, err := url.Parse(buildURLRelativeToBase(defenderHosts))
  749. if err != nil {
  750. return response, body, err
  751. }
  752. resp, err := sendHTTPRequest(http.MethodGet, url.String(), nil, "", getDefaultToken())
  753. if err != nil {
  754. return response, body, err
  755. }
  756. defer resp.Body.Close()
  757. err = checkResponse(resp.StatusCode, expectedStatusCode)
  758. if err == nil && expectedStatusCode == http.StatusOK {
  759. err = render.DecodeJSON(resp.Body, &response)
  760. } else {
  761. body, _ = getResponseBody(resp)
  762. }
  763. return response, body, err
  764. }
  765. // GetDefenderHostByIP returns the host with the given IP, if it exists
  766. func GetDefenderHostByIP(ip string, expectedStatusCode int) (common.DefenderEntry, []byte, error) {
  767. var host common.DefenderEntry
  768. var body []byte
  769. id := hex.EncodeToString([]byte(ip))
  770. resp, err := sendHTTPRequest(http.MethodGet, buildURLRelativeToBase(defenderHosts, id),
  771. nil, "", getDefaultToken())
  772. if err != nil {
  773. return host, body, err
  774. }
  775. defer resp.Body.Close()
  776. err = checkResponse(resp.StatusCode, expectedStatusCode)
  777. if err == nil && expectedStatusCode == http.StatusOK {
  778. err = render.DecodeJSON(resp.Body, &host)
  779. } else {
  780. body, _ = getResponseBody(resp)
  781. }
  782. return host, body, err
  783. }
  784. // RemoveDefenderHostByIP removes the host with the given IP from the defender list
  785. func RemoveDefenderHostByIP(ip string, expectedStatusCode int) ([]byte, error) {
  786. var body []byte
  787. id := hex.EncodeToString([]byte(ip))
  788. resp, err := sendHTTPRequest(http.MethodDelete, buildURLRelativeToBase(defenderHosts, id), nil, "", getDefaultToken())
  789. if err != nil {
  790. return body, err
  791. }
  792. defer resp.Body.Close()
  793. body, _ = getResponseBody(resp)
  794. return body, checkResponse(resp.StatusCode, expectedStatusCode)
  795. }
  796. // GetBanTime returns the ban time for the given IP address
  797. func GetBanTime(ip string, expectedStatusCode int) (map[string]interface{}, []byte, error) {
  798. var response map[string]interface{}
  799. var body []byte
  800. url, err := url.Parse(buildURLRelativeToBase(defenderBanTime))
  801. if err != nil {
  802. return response, body, err
  803. }
  804. q := url.Query()
  805. q.Add("ip", ip)
  806. url.RawQuery = q.Encode()
  807. resp, err := sendHTTPRequest(http.MethodGet, url.String(), nil, "", getDefaultToken())
  808. if err != nil {
  809. return response, body, err
  810. }
  811. defer resp.Body.Close()
  812. err = checkResponse(resp.StatusCode, expectedStatusCode)
  813. if err == nil && expectedStatusCode == http.StatusOK {
  814. err = render.DecodeJSON(resp.Body, &response)
  815. } else {
  816. body, _ = getResponseBody(resp)
  817. }
  818. return response, body, err
  819. }
  820. // GetScore returns the score for the given IP address
  821. func GetScore(ip string, expectedStatusCode int) (map[string]interface{}, []byte, error) {
  822. var response map[string]interface{}
  823. var body []byte
  824. url, err := url.Parse(buildURLRelativeToBase(defenderScore))
  825. if err != nil {
  826. return response, body, err
  827. }
  828. q := url.Query()
  829. q.Add("ip", ip)
  830. url.RawQuery = q.Encode()
  831. resp, err := sendHTTPRequest(http.MethodGet, url.String(), nil, "", getDefaultToken())
  832. if err != nil {
  833. return response, body, err
  834. }
  835. defer resp.Body.Close()
  836. err = checkResponse(resp.StatusCode, expectedStatusCode)
  837. if err == nil && expectedStatusCode == http.StatusOK {
  838. err = render.DecodeJSON(resp.Body, &response)
  839. } else {
  840. body, _ = getResponseBody(resp)
  841. }
  842. return response, body, err
  843. }
  844. // UnbanIP unbans the given IP address
  845. func UnbanIP(ip string, expectedStatusCode int) error {
  846. postBody := make(map[string]string)
  847. postBody["ip"] = ip
  848. asJSON, _ := json.Marshal(postBody)
  849. resp, err := sendHTTPRequest(http.MethodPost, buildURLRelativeToBase(defenderUnban), bytes.NewBuffer(asJSON),
  850. "", getDefaultToken())
  851. if err != nil {
  852. return err
  853. }
  854. defer resp.Body.Close()
  855. return checkResponse(resp.StatusCode, expectedStatusCode)
  856. }
  857. // Dumpdata requests a backup to outputFile.
  858. // outputFile is relative to the configured backups_path
  859. func Dumpdata(outputFile, outputData, indent string, expectedStatusCode int) (map[string]interface{}, []byte, error) {
  860. var response map[string]interface{}
  861. var body []byte
  862. url, err := url.Parse(buildURLRelativeToBase(dumpDataPath))
  863. if err != nil {
  864. return response, body, err
  865. }
  866. q := url.Query()
  867. if outputData != "" {
  868. q.Add("output-data", outputData)
  869. }
  870. if outputFile != "" {
  871. q.Add("output-file", outputFile)
  872. }
  873. if indent != "" {
  874. q.Add("indent", indent)
  875. }
  876. url.RawQuery = q.Encode()
  877. resp, err := sendHTTPRequest(http.MethodGet, url.String(), nil, "", getDefaultToken())
  878. if err != nil {
  879. return response, body, err
  880. }
  881. defer resp.Body.Close()
  882. err = checkResponse(resp.StatusCode, expectedStatusCode)
  883. if err == nil && expectedStatusCode == http.StatusOK {
  884. err = render.DecodeJSON(resp.Body, &response)
  885. } else {
  886. body, _ = getResponseBody(resp)
  887. }
  888. return response, body, err
  889. }
  890. // Loaddata restores a backup.
  891. func Loaddata(inputFile, scanQuota, mode string, expectedStatusCode int) (map[string]interface{}, []byte, error) {
  892. var response map[string]interface{}
  893. var body []byte
  894. url, err := url.Parse(buildURLRelativeToBase(loadDataPath))
  895. if err != nil {
  896. return response, body, err
  897. }
  898. q := url.Query()
  899. q.Add("input-file", inputFile)
  900. if scanQuota != "" {
  901. q.Add("scan-quota", scanQuota)
  902. }
  903. if mode != "" {
  904. q.Add("mode", mode)
  905. }
  906. url.RawQuery = q.Encode()
  907. resp, err := sendHTTPRequest(http.MethodGet, url.String(), nil, "", getDefaultToken())
  908. if err != nil {
  909. return response, body, err
  910. }
  911. defer resp.Body.Close()
  912. err = checkResponse(resp.StatusCode, expectedStatusCode)
  913. if err == nil && expectedStatusCode == http.StatusOK {
  914. err = render.DecodeJSON(resp.Body, &response)
  915. } else {
  916. body, _ = getResponseBody(resp)
  917. }
  918. return response, body, err
  919. }
  920. // LoaddataFromPostBody restores a backup
  921. func LoaddataFromPostBody(data []byte, scanQuota, mode string, expectedStatusCode int) (map[string]interface{}, []byte, error) {
  922. var response map[string]interface{}
  923. var body []byte
  924. url, err := url.Parse(buildURLRelativeToBase(loadDataPath))
  925. if err != nil {
  926. return response, body, err
  927. }
  928. q := url.Query()
  929. if scanQuota != "" {
  930. q.Add("scan-quota", scanQuota)
  931. }
  932. if mode != "" {
  933. q.Add("mode", mode)
  934. }
  935. url.RawQuery = q.Encode()
  936. resp, err := sendHTTPRequest(http.MethodPost, url.String(), bytes.NewReader(data), "", getDefaultToken())
  937. if err != nil {
  938. return response, body, err
  939. }
  940. defer resp.Body.Close()
  941. err = checkResponse(resp.StatusCode, expectedStatusCode)
  942. if err == nil && expectedStatusCode == http.StatusOK {
  943. err = render.DecodeJSON(resp.Body, &response)
  944. } else {
  945. body, _ = getResponseBody(resp)
  946. }
  947. return response, body, err
  948. }
  949. func checkResponse(actual int, expected int) error {
  950. if expected != actual {
  951. return fmt.Errorf("wrong status code: got %v want %v", actual, expected)
  952. }
  953. return nil
  954. }
  955. func getResponseBody(resp *http.Response) ([]byte, error) {
  956. return io.ReadAll(resp.Body)
  957. }
  958. func checkFolder(expected *vfs.BaseVirtualFolder, actual *vfs.BaseVirtualFolder) error {
  959. if expected.ID <= 0 {
  960. if actual.ID <= 0 {
  961. return errors.New("actual folder ID must be > 0")
  962. }
  963. } else {
  964. if actual.ID != expected.ID {
  965. return errors.New("folder ID mismatch")
  966. }
  967. }
  968. if expected.Name != actual.Name {
  969. return errors.New("name mismatch")
  970. }
  971. if expected.MappedPath != actual.MappedPath {
  972. return errors.New("mapped path mismatch")
  973. }
  974. if expected.Description != actual.Description {
  975. return errors.New("description mismatch")
  976. }
  977. return compareFsConfig(&expected.FsConfig, &actual.FsConfig)
  978. }
  979. func checkAPIKey(expected, actual *dataprovider.APIKey) error {
  980. if actual.Key != "" {
  981. return errors.New("key must not be visible")
  982. }
  983. if actual.KeyID == "" {
  984. return errors.New("actual key_id cannot be empty")
  985. }
  986. if expected.Name != actual.Name {
  987. return errors.New("name mismatch")
  988. }
  989. if expected.Scope != actual.Scope {
  990. return errors.New("scope mismatch")
  991. }
  992. if actual.CreatedAt == 0 {
  993. return errors.New("created_at cannot be 0")
  994. }
  995. if actual.UpdatedAt == 0 {
  996. return errors.New("updated_at cannot be 0")
  997. }
  998. if expected.ExpiresAt != actual.ExpiresAt {
  999. return errors.New("expires_at mismatch")
  1000. }
  1001. if expected.Description != actual.Description {
  1002. return errors.New("description mismatch")
  1003. }
  1004. if expected.User != actual.User {
  1005. return errors.New("user mismatch")
  1006. }
  1007. if expected.Admin != actual.Admin {
  1008. return errors.New("admin mismatch")
  1009. }
  1010. return nil
  1011. }
  1012. func checkAdmin(expected, actual *dataprovider.Admin) error {
  1013. if actual.Password != "" {
  1014. return errors.New("admin password must not be visible")
  1015. }
  1016. if expected.ID <= 0 {
  1017. if actual.ID <= 0 {
  1018. return errors.New("actual admin ID must be > 0")
  1019. }
  1020. } else {
  1021. if actual.ID != expected.ID {
  1022. return errors.New("admin ID mismatch")
  1023. }
  1024. }
  1025. if expected.CreatedAt > 0 {
  1026. if expected.CreatedAt != actual.CreatedAt {
  1027. return fmt.Errorf("created_at mismatch %v != %v", expected.CreatedAt, actual.CreatedAt)
  1028. }
  1029. }
  1030. if err := compareAdminEqualFields(expected, actual); err != nil {
  1031. return err
  1032. }
  1033. if len(expected.Permissions) != len(actual.Permissions) {
  1034. return errors.New("permissions mismatch")
  1035. }
  1036. for _, p := range expected.Permissions {
  1037. if !util.IsStringInSlice(p, actual.Permissions) {
  1038. return errors.New("permissions content mismatch")
  1039. }
  1040. }
  1041. if len(expected.Filters.AllowList) != len(actual.Filters.AllowList) {
  1042. return errors.New("allow list mismatch")
  1043. }
  1044. if expected.Filters.AllowAPIKeyAuth != actual.Filters.AllowAPIKeyAuth {
  1045. return errors.New("allow_api_key_auth mismatch")
  1046. }
  1047. for _, v := range expected.Filters.AllowList {
  1048. if !util.IsStringInSlice(v, actual.Filters.AllowList) {
  1049. return errors.New("allow list content mismatch")
  1050. }
  1051. }
  1052. return nil
  1053. }
  1054. func compareAdminEqualFields(expected *dataprovider.Admin, actual *dataprovider.Admin) error {
  1055. if expected.Username != actual.Username {
  1056. return errors.New("sername mismatch")
  1057. }
  1058. if expected.Email != actual.Email {
  1059. return errors.New("email mismatch")
  1060. }
  1061. if expected.Status != actual.Status {
  1062. return errors.New("status mismatch")
  1063. }
  1064. if expected.Description != actual.Description {
  1065. return errors.New("description mismatch")
  1066. }
  1067. if expected.AdditionalInfo != actual.AdditionalInfo {
  1068. return errors.New("additional info mismatch")
  1069. }
  1070. return nil
  1071. }
  1072. func checkUser(expected *dataprovider.User, actual *dataprovider.User) error {
  1073. if actual.Password != "" {
  1074. return errors.New("user password must not be visible")
  1075. }
  1076. if expected.ID <= 0 {
  1077. if actual.ID <= 0 {
  1078. return errors.New("actual user ID must be > 0")
  1079. }
  1080. } else {
  1081. if actual.ID != expected.ID {
  1082. return errors.New("user ID mismatch")
  1083. }
  1084. }
  1085. if expected.CreatedAt > 0 {
  1086. if expected.CreatedAt != actual.CreatedAt {
  1087. return fmt.Errorf("created_at mismatch %v != %v", expected.CreatedAt, actual.CreatedAt)
  1088. }
  1089. }
  1090. if expected.Email != actual.Email {
  1091. return errors.New("email mismatch")
  1092. }
  1093. if err := compareUserPermissions(expected, actual); err != nil {
  1094. return err
  1095. }
  1096. if err := compareUserFilters(expected, actual); err != nil {
  1097. return err
  1098. }
  1099. if err := compareFsConfig(&expected.FsConfig, &actual.FsConfig); err != nil {
  1100. return err
  1101. }
  1102. if err := compareUserVirtualFolders(expected, actual); err != nil {
  1103. return err
  1104. }
  1105. return compareEqualsUserFields(expected, actual)
  1106. }
  1107. func compareUserPermissions(expected *dataprovider.User, actual *dataprovider.User) error {
  1108. if len(expected.Permissions) != len(actual.Permissions) {
  1109. return errors.New("permissions mismatch")
  1110. }
  1111. for dir, perms := range expected.Permissions {
  1112. if actualPerms, ok := actual.Permissions[dir]; ok {
  1113. for _, v := range actualPerms {
  1114. if !util.IsStringInSlice(v, perms) {
  1115. return errors.New("permissions contents mismatch")
  1116. }
  1117. }
  1118. } else {
  1119. return errors.New("permissions directories mismatch")
  1120. }
  1121. }
  1122. return nil
  1123. }
  1124. func compareUserVirtualFolders(expected *dataprovider.User, actual *dataprovider.User) error {
  1125. if len(actual.VirtualFolders) != len(expected.VirtualFolders) {
  1126. return errors.New("virtual folders len mismatch")
  1127. }
  1128. for _, v := range actual.VirtualFolders {
  1129. found := false
  1130. for _, v1 := range expected.VirtualFolders {
  1131. if path.Clean(v.VirtualPath) == path.Clean(v1.VirtualPath) {
  1132. if err := checkFolder(&v1.BaseVirtualFolder, &v.BaseVirtualFolder); err != nil {
  1133. return err
  1134. }
  1135. if v.QuotaSize != v1.QuotaSize {
  1136. return errors.New("vfolder quota size mismatch")
  1137. }
  1138. if (v.QuotaFiles) != (v1.QuotaFiles) {
  1139. return errors.New("vfolder quota files mismatch")
  1140. }
  1141. found = true
  1142. break
  1143. }
  1144. }
  1145. if !found {
  1146. return errors.New("virtual folders mismatch")
  1147. }
  1148. }
  1149. return nil
  1150. }
  1151. func compareFsConfig(expected *vfs.Filesystem, actual *vfs.Filesystem) error {
  1152. if expected.Provider != actual.Provider {
  1153. return errors.New("fs provider mismatch")
  1154. }
  1155. if err := compareS3Config(expected, actual); err != nil {
  1156. return err
  1157. }
  1158. if err := compareGCSConfig(expected, actual); err != nil {
  1159. return err
  1160. }
  1161. if err := compareAzBlobConfig(expected, actual); err != nil {
  1162. return err
  1163. }
  1164. if err := checkEncryptedSecret(expected.CryptConfig.Passphrase, actual.CryptConfig.Passphrase); err != nil {
  1165. return err
  1166. }
  1167. return compareSFTPFsConfig(expected, actual)
  1168. }
  1169. func compareS3Config(expected *vfs.Filesystem, actual *vfs.Filesystem) error { //nolint:gocyclo
  1170. if expected.S3Config.Bucket != actual.S3Config.Bucket {
  1171. return errors.New("fs S3 bucket mismatch")
  1172. }
  1173. if expected.S3Config.Region != actual.S3Config.Region {
  1174. return errors.New("fs S3 region mismatch")
  1175. }
  1176. if expected.S3Config.AccessKey != actual.S3Config.AccessKey {
  1177. return errors.New("fs S3 access key mismatch")
  1178. }
  1179. if err := checkEncryptedSecret(expected.S3Config.AccessSecret, actual.S3Config.AccessSecret); err != nil {
  1180. return fmt.Errorf("fs S3 access secret mismatch: %v", err)
  1181. }
  1182. if expected.S3Config.Endpoint != actual.S3Config.Endpoint {
  1183. return errors.New("fs S3 endpoint mismatch")
  1184. }
  1185. if expected.S3Config.StorageClass != actual.S3Config.StorageClass {
  1186. return errors.New("fs S3 storage class mismatch")
  1187. }
  1188. if expected.S3Config.ACL != actual.S3Config.ACL {
  1189. return errors.New("fs S3 ACL mismatch")
  1190. }
  1191. if expected.S3Config.UploadPartSize != actual.S3Config.UploadPartSize {
  1192. return errors.New("fs S3 upload part size mismatch")
  1193. }
  1194. if expected.S3Config.UploadConcurrency != actual.S3Config.UploadConcurrency {
  1195. return errors.New("fs S3 upload concurrency mismatch")
  1196. }
  1197. if expected.S3Config.DownloadPartSize != actual.S3Config.DownloadPartSize {
  1198. return errors.New("fs S3 download part size mismatch")
  1199. }
  1200. if expected.S3Config.DownloadConcurrency != actual.S3Config.DownloadConcurrency {
  1201. return errors.New("fs S3 download concurrency mismatch")
  1202. }
  1203. if expected.S3Config.ForcePathStyle != actual.S3Config.ForcePathStyle {
  1204. return errors.New("fs S3 force path style mismatch")
  1205. }
  1206. if expected.S3Config.DownloadPartMaxTime != actual.S3Config.DownloadPartMaxTime {
  1207. return errors.New("fs S3 download part max time mismatch")
  1208. }
  1209. if expected.S3Config.KeyPrefix != actual.S3Config.KeyPrefix &&
  1210. expected.S3Config.KeyPrefix+"/" != actual.S3Config.KeyPrefix {
  1211. return errors.New("fs S3 key prefix mismatch")
  1212. }
  1213. return nil
  1214. }
  1215. func compareGCSConfig(expected *vfs.Filesystem, actual *vfs.Filesystem) error {
  1216. if expected.GCSConfig.Bucket != actual.GCSConfig.Bucket {
  1217. return errors.New("GCS bucket mismatch")
  1218. }
  1219. if expected.GCSConfig.StorageClass != actual.GCSConfig.StorageClass {
  1220. return errors.New("GCS storage class mismatch")
  1221. }
  1222. if expected.GCSConfig.KeyPrefix != actual.GCSConfig.KeyPrefix &&
  1223. expected.GCSConfig.KeyPrefix+"/" != actual.GCSConfig.KeyPrefix {
  1224. return errors.New("GCS key prefix mismatch")
  1225. }
  1226. if expected.GCSConfig.AutomaticCredentials != actual.GCSConfig.AutomaticCredentials {
  1227. return errors.New("GCS automatic credentials mismatch")
  1228. }
  1229. return nil
  1230. }
  1231. func compareSFTPFsConfig(expected *vfs.Filesystem, actual *vfs.Filesystem) error {
  1232. if expected.SFTPConfig.Endpoint != actual.SFTPConfig.Endpoint {
  1233. return errors.New("SFTPFs endpoint mismatch")
  1234. }
  1235. if expected.SFTPConfig.Username != actual.SFTPConfig.Username {
  1236. return errors.New("SFTPFs username mismatch")
  1237. }
  1238. if expected.SFTPConfig.DisableCouncurrentReads != actual.SFTPConfig.DisableCouncurrentReads {
  1239. return errors.New("SFTPFs disable_concurrent_reads mismatch")
  1240. }
  1241. if expected.SFTPConfig.BufferSize != actual.SFTPConfig.BufferSize {
  1242. return errors.New("SFTPFs buffer_size mismatch")
  1243. }
  1244. if err := checkEncryptedSecret(expected.SFTPConfig.Password, actual.SFTPConfig.Password); err != nil {
  1245. return fmt.Errorf("SFTPFs password mismatch: %v", err)
  1246. }
  1247. if err := checkEncryptedSecret(expected.SFTPConfig.PrivateKey, actual.SFTPConfig.PrivateKey); err != nil {
  1248. return fmt.Errorf("SFTPFs private key mismatch: %v", err)
  1249. }
  1250. if expected.SFTPConfig.Prefix != actual.SFTPConfig.Prefix {
  1251. if expected.SFTPConfig.Prefix != "" && actual.SFTPConfig.Prefix != "/" {
  1252. return errors.New("SFTPFs prefix mismatch")
  1253. }
  1254. }
  1255. if len(expected.SFTPConfig.Fingerprints) != len(actual.SFTPConfig.Fingerprints) {
  1256. return errors.New("SFTPFs fingerprints mismatch")
  1257. }
  1258. for _, value := range actual.SFTPConfig.Fingerprints {
  1259. if !util.IsStringInSlice(value, expected.SFTPConfig.Fingerprints) {
  1260. return errors.New("SFTPFs fingerprints mismatch")
  1261. }
  1262. }
  1263. return nil
  1264. }
  1265. func compareAzBlobConfig(expected *vfs.Filesystem, actual *vfs.Filesystem) error {
  1266. if expected.AzBlobConfig.Container != actual.AzBlobConfig.Container {
  1267. return errors.New("azure Blob container mismatch")
  1268. }
  1269. if expected.AzBlobConfig.AccountName != actual.AzBlobConfig.AccountName {
  1270. return errors.New("azure Blob account name mismatch")
  1271. }
  1272. if err := checkEncryptedSecret(expected.AzBlobConfig.AccountKey, actual.AzBlobConfig.AccountKey); err != nil {
  1273. return fmt.Errorf("azure Blob account key mismatch: %v", err)
  1274. }
  1275. if expected.AzBlobConfig.Endpoint != actual.AzBlobConfig.Endpoint {
  1276. return errors.New("azure Blob endpoint mismatch")
  1277. }
  1278. if err := checkEncryptedSecret(expected.AzBlobConfig.SASURL, actual.AzBlobConfig.SASURL); err != nil {
  1279. return fmt.Errorf("azure Blob SAS URL mismatch: %v", err)
  1280. }
  1281. if expected.AzBlobConfig.UploadPartSize != actual.AzBlobConfig.UploadPartSize {
  1282. return errors.New("azure Blob upload part size mismatch")
  1283. }
  1284. if expected.AzBlobConfig.UploadConcurrency != actual.AzBlobConfig.UploadConcurrency {
  1285. return errors.New("azure Blob upload concurrency mismatch")
  1286. }
  1287. if expected.AzBlobConfig.KeyPrefix != actual.AzBlobConfig.KeyPrefix &&
  1288. expected.AzBlobConfig.KeyPrefix+"/" != actual.AzBlobConfig.KeyPrefix {
  1289. return errors.New("azure Blob key prefix mismatch")
  1290. }
  1291. if expected.AzBlobConfig.UseEmulator != actual.AzBlobConfig.UseEmulator {
  1292. return errors.New("azure Blob use emulator mismatch")
  1293. }
  1294. if expected.AzBlobConfig.AccessTier != actual.AzBlobConfig.AccessTier {
  1295. return errors.New("azure Blob access tier mismatch")
  1296. }
  1297. return nil
  1298. }
  1299. func areSecretEquals(expected, actual *kms.Secret) bool {
  1300. if expected == nil && actual == nil {
  1301. return true
  1302. }
  1303. if expected != nil && expected.IsEmpty() && actual == nil {
  1304. return true
  1305. }
  1306. if actual != nil && actual.IsEmpty() && expected == nil {
  1307. return true
  1308. }
  1309. return false
  1310. }
  1311. func checkEncryptedSecret(expected, actual *kms.Secret) error {
  1312. if areSecretEquals(expected, actual) {
  1313. return nil
  1314. }
  1315. if expected == nil && actual != nil && !actual.IsEmpty() {
  1316. return errors.New("secret mismatch")
  1317. }
  1318. if actual == nil && expected != nil && !expected.IsEmpty() {
  1319. return errors.New("secret mismatch")
  1320. }
  1321. if expected.IsPlain() && actual.IsEncrypted() {
  1322. if actual.GetPayload() == "" {
  1323. return errors.New("invalid secret payload")
  1324. }
  1325. if actual.GetAdditionalData() != "" {
  1326. return errors.New("invalid secret additional data")
  1327. }
  1328. if actual.GetKey() != "" {
  1329. return errors.New("invalid secret key")
  1330. }
  1331. } else {
  1332. if expected.GetStatus() != actual.GetStatus() || expected.GetPayload() != actual.GetPayload() {
  1333. return errors.New("secret mismatch")
  1334. }
  1335. }
  1336. return nil
  1337. }
  1338. func compareUserFilterSubStructs(expected *dataprovider.User, actual *dataprovider.User) error {
  1339. for _, IPMask := range expected.Filters.AllowedIP {
  1340. if !util.IsStringInSlice(IPMask, actual.Filters.AllowedIP) {
  1341. return errors.New("allowed IP contents mismatch")
  1342. }
  1343. }
  1344. for _, IPMask := range expected.Filters.DeniedIP {
  1345. if !util.IsStringInSlice(IPMask, actual.Filters.DeniedIP) {
  1346. return errors.New("denied IP contents mismatch")
  1347. }
  1348. }
  1349. for _, method := range expected.Filters.DeniedLoginMethods {
  1350. if !util.IsStringInSlice(method, actual.Filters.DeniedLoginMethods) {
  1351. return errors.New("denied login methods contents mismatch")
  1352. }
  1353. }
  1354. for _, protocol := range expected.Filters.DeniedProtocols {
  1355. if !util.IsStringInSlice(protocol, actual.Filters.DeniedProtocols) {
  1356. return errors.New("denied protocols contents mismatch")
  1357. }
  1358. }
  1359. for _, options := range expected.Filters.WebClient {
  1360. if !util.IsStringInSlice(options, actual.Filters.WebClient) {
  1361. return errors.New("web client options contents mismatch")
  1362. }
  1363. }
  1364. if expected.Filters.Hooks.ExternalAuthDisabled != actual.Filters.Hooks.ExternalAuthDisabled {
  1365. return errors.New("external_auth_disabled hook mismatch")
  1366. }
  1367. if expected.Filters.Hooks.PreLoginDisabled != actual.Filters.Hooks.PreLoginDisabled {
  1368. return errors.New("pre_login_disabled hook mismatch")
  1369. }
  1370. if expected.Filters.Hooks.CheckPasswordDisabled != actual.Filters.Hooks.CheckPasswordDisabled {
  1371. return errors.New("check_password_disabled hook mismatch")
  1372. }
  1373. if expected.Filters.DisableFsChecks != actual.Filters.DisableFsChecks {
  1374. return errors.New("disable_fs_checks mismatch")
  1375. }
  1376. return nil
  1377. }
  1378. func compareUserFilters(expected *dataprovider.User, actual *dataprovider.User) error {
  1379. if len(expected.Filters.AllowedIP) != len(actual.Filters.AllowedIP) {
  1380. return errors.New("allowed IP mismatch")
  1381. }
  1382. if len(expected.Filters.DeniedIP) != len(actual.Filters.DeniedIP) {
  1383. return errors.New("denied IP mismatch")
  1384. }
  1385. if len(expected.Filters.DeniedLoginMethods) != len(actual.Filters.DeniedLoginMethods) {
  1386. return errors.New("denied login methods mismatch")
  1387. }
  1388. if len(expected.Filters.DeniedProtocols) != len(actual.Filters.DeniedProtocols) {
  1389. return errors.New("denied protocols mismatch")
  1390. }
  1391. if expected.Filters.MaxUploadFileSize != actual.Filters.MaxUploadFileSize {
  1392. return errors.New("max upload file size mismatch")
  1393. }
  1394. if expected.Filters.TLSUsername != actual.Filters.TLSUsername {
  1395. return errors.New("TLSUsername mismatch")
  1396. }
  1397. if len(expected.Filters.WebClient) != len(actual.Filters.WebClient) {
  1398. return errors.New("WebClient filter mismatch")
  1399. }
  1400. if expected.Filters.AllowAPIKeyAuth != actual.Filters.AllowAPIKeyAuth {
  1401. return errors.New("allow_api_key_auth mismatch")
  1402. }
  1403. if err := compareUserFilterSubStructs(expected, actual); err != nil {
  1404. return err
  1405. }
  1406. return compareUserFilePatternsFilters(expected, actual)
  1407. }
  1408. func checkFilterMatch(expected []string, actual []string) bool {
  1409. if len(expected) != len(actual) {
  1410. return false
  1411. }
  1412. for _, e := range expected {
  1413. if !util.IsStringInSlice(strings.ToLower(e), actual) {
  1414. return false
  1415. }
  1416. }
  1417. return true
  1418. }
  1419. func compareUserFilePatternsFilters(expected *dataprovider.User, actual *dataprovider.User) error {
  1420. if len(expected.Filters.FilePatterns) != len(actual.Filters.FilePatterns) {
  1421. return errors.New("file patterns mismatch")
  1422. }
  1423. for _, f := range expected.Filters.FilePatterns {
  1424. found := false
  1425. for _, f1 := range actual.Filters.FilePatterns {
  1426. if path.Clean(f.Path) == path.Clean(f1.Path) {
  1427. if !checkFilterMatch(f.AllowedPatterns, f1.AllowedPatterns) ||
  1428. !checkFilterMatch(f.DeniedPatterns, f1.DeniedPatterns) {
  1429. return errors.New("file patterns contents mismatch")
  1430. }
  1431. found = true
  1432. }
  1433. }
  1434. if !found {
  1435. return errors.New("file patterns contents mismatch")
  1436. }
  1437. }
  1438. return nil
  1439. }
  1440. func compareEqualsUserFields(expected *dataprovider.User, actual *dataprovider.User) error {
  1441. if expected.Username != actual.Username {
  1442. return errors.New("username mismatch")
  1443. }
  1444. if expected.HomeDir != actual.HomeDir {
  1445. return errors.New("home dir mismatch")
  1446. }
  1447. if expected.UID != actual.UID {
  1448. return errors.New("UID mismatch")
  1449. }
  1450. if expected.GID != actual.GID {
  1451. return errors.New("GID mismatch")
  1452. }
  1453. if expected.MaxSessions != actual.MaxSessions {
  1454. return errors.New("MaxSessions mismatch")
  1455. }
  1456. if expected.QuotaSize != actual.QuotaSize {
  1457. return errors.New("QuotaSize mismatch")
  1458. }
  1459. if expected.QuotaFiles != actual.QuotaFiles {
  1460. return errors.New("QuotaFiles mismatch")
  1461. }
  1462. if len(expected.Permissions) != len(actual.Permissions) {
  1463. return errors.New("permissions mismatch")
  1464. }
  1465. if expected.UploadBandwidth != actual.UploadBandwidth {
  1466. return errors.New("UploadBandwidth mismatch")
  1467. }
  1468. if expected.DownloadBandwidth != actual.DownloadBandwidth {
  1469. return errors.New("DownloadBandwidth mismatch")
  1470. }
  1471. if expected.Status != actual.Status {
  1472. return errors.New("status mismatch")
  1473. }
  1474. if expected.ExpirationDate != actual.ExpirationDate {
  1475. return errors.New("ExpirationDate mismatch")
  1476. }
  1477. if expected.AdditionalInfo != actual.AdditionalInfo {
  1478. return errors.New("AdditionalInfo mismatch")
  1479. }
  1480. if expected.Description != actual.Description {
  1481. return errors.New("description mismatch")
  1482. }
  1483. return nil
  1484. }
  1485. func addLimitAndOffsetQueryParams(rawurl string, limit, offset int64) (*url.URL, error) {
  1486. url, err := url.Parse(rawurl)
  1487. if err != nil {
  1488. return nil, err
  1489. }
  1490. q := url.Query()
  1491. if limit > 0 {
  1492. q.Add("limit", strconv.FormatInt(limit, 10))
  1493. }
  1494. if offset > 0 {
  1495. q.Add("offset", strconv.FormatInt(offset, 10))
  1496. }
  1497. url.RawQuery = q.Encode()
  1498. return url, err
  1499. }
  1500. func addModeQueryParam(rawurl, mode string) (*url.URL, error) {
  1501. url, err := url.Parse(rawurl)
  1502. if err != nil {
  1503. return nil, err
  1504. }
  1505. q := url.Query()
  1506. if len(mode) > 0 {
  1507. q.Add("mode", mode)
  1508. }
  1509. url.RawQuery = q.Encode()
  1510. return url, err
  1511. }
  1512. func addDisconnectQueryParam(rawurl, disconnect string) (*url.URL, error) {
  1513. url, err := url.Parse(rawurl)
  1514. if err != nil {
  1515. return nil, err
  1516. }
  1517. q := url.Query()
  1518. if len(disconnect) > 0 {
  1519. q.Add("disconnect", disconnect)
  1520. }
  1521. url.RawQuery = q.Encode()
  1522. return url, err
  1523. }