api_quota.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. package httpd
  2. import (
  3. "errors"
  4. "fmt"
  5. "net/http"
  6. "github.com/go-chi/render"
  7. "github.com/drakkan/sftpgo/v2/common"
  8. "github.com/drakkan/sftpgo/v2/dataprovider"
  9. "github.com/drakkan/sftpgo/v2/logger"
  10. "github.com/drakkan/sftpgo/v2/vfs"
  11. )
  12. const (
  13. quotaUpdateModeAdd = "add"
  14. quotaUpdateModeReset = "reset"
  15. )
  16. type quotaUsage struct {
  17. UsedQuotaSize int64 `json:"used_quota_size"`
  18. UsedQuotaFiles int `json:"used_quota_files"`
  19. }
  20. type transferQuotaUsage struct {
  21. UsedUploadDataTransfer int64 `json:"used_upload_data_transfer"`
  22. UsedDownloadDataTransfer int64 `json:"used_download_data_transfer"`
  23. }
  24. func getUsersQuotaScans(w http.ResponseWriter, r *http.Request) {
  25. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  26. render.JSON(w, r, common.QuotaScans.GetUsersQuotaScans())
  27. }
  28. func getFoldersQuotaScans(w http.ResponseWriter, r *http.Request) {
  29. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  30. render.JSON(w, r, common.QuotaScans.GetVFoldersQuotaScans())
  31. }
  32. func updateUserQuotaUsage(w http.ResponseWriter, r *http.Request) {
  33. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  34. var usage quotaUsage
  35. err := render.DecodeJSON(r.Body, &usage)
  36. if err != nil {
  37. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  38. return
  39. }
  40. doUpdateUserQuotaUsage(w, r, getURLParam(r, "username"), usage)
  41. }
  42. func updateUserQuotaUsageCompat(w http.ResponseWriter, r *http.Request) {
  43. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  44. var u dataprovider.User
  45. err := render.DecodeJSON(r.Body, &u)
  46. if err != nil {
  47. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  48. return
  49. }
  50. usage := quotaUsage{
  51. UsedQuotaSize: u.UsedQuotaSize,
  52. UsedQuotaFiles: u.UsedQuotaFiles,
  53. }
  54. doUpdateUserQuotaUsage(w, r, u.Username, usage)
  55. }
  56. func updateFolderQuotaUsage(w http.ResponseWriter, r *http.Request) {
  57. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  58. var usage quotaUsage
  59. err := render.DecodeJSON(r.Body, &usage)
  60. if err != nil {
  61. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  62. return
  63. }
  64. doUpdateFolderQuotaUsage(w, r, getURLParam(r, "name"), usage)
  65. }
  66. func updateFolderQuotaUsageCompat(w http.ResponseWriter, r *http.Request) {
  67. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  68. var f vfs.BaseVirtualFolder
  69. err := render.DecodeJSON(r.Body, &f)
  70. if err != nil {
  71. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  72. return
  73. }
  74. usage := quotaUsage{
  75. UsedQuotaSize: f.UsedQuotaSize,
  76. UsedQuotaFiles: f.UsedQuotaFiles,
  77. }
  78. doUpdateFolderQuotaUsage(w, r, f.Name, usage)
  79. }
  80. func startUserQuotaScan(w http.ResponseWriter, r *http.Request) {
  81. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  82. doStartUserQuotaScan(w, r, getURLParam(r, "username"))
  83. }
  84. func startUserQuotaScanCompat(w http.ResponseWriter, r *http.Request) {
  85. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  86. var u dataprovider.User
  87. err := render.DecodeJSON(r.Body, &u)
  88. if err != nil {
  89. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  90. return
  91. }
  92. doStartUserQuotaScan(w, r, u.Username)
  93. }
  94. func startFolderQuotaScan(w http.ResponseWriter, r *http.Request) {
  95. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  96. doStartFolderQuotaScan(w, r, getURLParam(r, "name"))
  97. }
  98. func startFolderQuotaScanCompat(w http.ResponseWriter, r *http.Request) {
  99. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  100. var f vfs.BaseVirtualFolder
  101. err := render.DecodeJSON(r.Body, &f)
  102. if err != nil {
  103. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  104. return
  105. }
  106. doStartFolderQuotaScan(w, r, f.Name)
  107. }
  108. func updateUserTransferQuotaUsage(w http.ResponseWriter, r *http.Request) {
  109. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  110. var usage transferQuotaUsage
  111. err := render.DecodeJSON(r.Body, &usage)
  112. if err != nil {
  113. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  114. return
  115. }
  116. if usage.UsedUploadDataTransfer < 0 || usage.UsedDownloadDataTransfer < 0 {
  117. sendAPIResponse(w, r, errors.New("invalid used transfer quota parameters, negative values are not allowed"),
  118. "", http.StatusBadRequest)
  119. return
  120. }
  121. mode, err := getQuotaUpdateMode(r)
  122. if err != nil {
  123. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  124. return
  125. }
  126. user, err := dataprovider.UserExists(getURLParam(r, "username"))
  127. if err != nil {
  128. sendAPIResponse(w, r, err, "", getRespStatus(err))
  129. return
  130. }
  131. if mode == quotaUpdateModeAdd && !user.HasTransferQuotaRestrictions() && dataprovider.GetQuotaTracking() == 2 {
  132. sendAPIResponse(w, r, errors.New("this user has no transfer quota restrictions, only reset mode is supported"),
  133. "", http.StatusBadRequest)
  134. return
  135. }
  136. err = dataprovider.UpdateUserTransferQuota(&user, usage.UsedUploadDataTransfer, usage.UsedDownloadDataTransfer,
  137. mode == quotaUpdateModeReset)
  138. if err != nil {
  139. sendAPIResponse(w, r, err, "", getRespStatus(err))
  140. return
  141. }
  142. sendAPIResponse(w, r, err, "Quota updated", http.StatusOK)
  143. }
  144. func doUpdateUserQuotaUsage(w http.ResponseWriter, r *http.Request, username string, usage quotaUsage) {
  145. if usage.UsedQuotaFiles < 0 || usage.UsedQuotaSize < 0 {
  146. sendAPIResponse(w, r, errors.New("invalid used quota parameters, negative values are not allowed"),
  147. "", http.StatusBadRequest)
  148. return
  149. }
  150. mode, err := getQuotaUpdateMode(r)
  151. if err != nil {
  152. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  153. return
  154. }
  155. user, err := dataprovider.UserExists(username)
  156. if err != nil {
  157. sendAPIResponse(w, r, err, "", getRespStatus(err))
  158. return
  159. }
  160. if mode == quotaUpdateModeAdd && !user.HasQuotaRestrictions() && dataprovider.GetQuotaTracking() == 2 {
  161. sendAPIResponse(w, r, errors.New("this user has no quota restrictions, only reset mode is supported"),
  162. "", http.StatusBadRequest)
  163. return
  164. }
  165. if !common.QuotaScans.AddUserQuotaScan(user.Username) {
  166. sendAPIResponse(w, r, err, "A quota scan is in progress for this user", http.StatusConflict)
  167. return
  168. }
  169. defer common.QuotaScans.RemoveUserQuotaScan(user.Username)
  170. err = dataprovider.UpdateUserQuota(&user, usage.UsedQuotaFiles, usage.UsedQuotaSize, mode == quotaUpdateModeReset)
  171. if err != nil {
  172. sendAPIResponse(w, r, err, "", getRespStatus(err))
  173. return
  174. }
  175. sendAPIResponse(w, r, err, "Quota updated", http.StatusOK)
  176. }
  177. func doUpdateFolderQuotaUsage(w http.ResponseWriter, r *http.Request, name string, usage quotaUsage) {
  178. if usage.UsedQuotaFiles < 0 || usage.UsedQuotaSize < 0 {
  179. sendAPIResponse(w, r, errors.New("invalid used quota parameters, negative values are not allowed"),
  180. "", http.StatusBadRequest)
  181. return
  182. }
  183. mode, err := getQuotaUpdateMode(r)
  184. if err != nil {
  185. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  186. return
  187. }
  188. folder, err := dataprovider.GetFolderByName(name)
  189. if err != nil {
  190. sendAPIResponse(w, r, err, "", getRespStatus(err))
  191. return
  192. }
  193. if !common.QuotaScans.AddVFolderQuotaScan(folder.Name) {
  194. sendAPIResponse(w, r, err, "A quota scan is in progress for this folder", http.StatusConflict)
  195. return
  196. }
  197. defer common.QuotaScans.RemoveVFolderQuotaScan(folder.Name)
  198. err = dataprovider.UpdateVirtualFolderQuota(&folder, usage.UsedQuotaFiles, usage.UsedQuotaSize, mode == quotaUpdateModeReset)
  199. if err != nil {
  200. sendAPIResponse(w, r, err, "", getRespStatus(err))
  201. } else {
  202. sendAPIResponse(w, r, err, "Quota updated", http.StatusOK)
  203. }
  204. }
  205. func doStartUserQuotaScan(w http.ResponseWriter, r *http.Request, username string) {
  206. if dataprovider.GetQuotaTracking() == 0 {
  207. sendAPIResponse(w, r, nil, "Quota tracking is disabled!", http.StatusForbidden)
  208. return
  209. }
  210. user, err := dataprovider.UserExists(username)
  211. if err != nil {
  212. sendAPIResponse(w, r, err, "", getRespStatus(err))
  213. return
  214. }
  215. if !common.QuotaScans.AddUserQuotaScan(user.Username) {
  216. sendAPIResponse(w, r, err, fmt.Sprintf("Another scan is already in progress for user %#v", username),
  217. http.StatusConflict)
  218. return
  219. }
  220. go doUserQuotaScan(user) //nolint:errcheck
  221. sendAPIResponse(w, r, err, "Scan started", http.StatusAccepted)
  222. }
  223. func doStartFolderQuotaScan(w http.ResponseWriter, r *http.Request, name string) {
  224. if dataprovider.GetQuotaTracking() == 0 {
  225. sendAPIResponse(w, r, nil, "Quota tracking is disabled!", http.StatusForbidden)
  226. return
  227. }
  228. folder, err := dataprovider.GetFolderByName(name)
  229. if err != nil {
  230. sendAPIResponse(w, r, err, "", getRespStatus(err))
  231. return
  232. }
  233. if !common.QuotaScans.AddVFolderQuotaScan(folder.Name) {
  234. sendAPIResponse(w, r, err, fmt.Sprintf("Another scan is already in progress for folder %#v", name),
  235. http.StatusConflict)
  236. return
  237. }
  238. go doFolderQuotaScan(folder) //nolint:errcheck
  239. sendAPIResponse(w, r, err, "Scan started", http.StatusAccepted)
  240. }
  241. func doUserQuotaScan(user dataprovider.User) error {
  242. defer common.QuotaScans.RemoveUserQuotaScan(user.Username)
  243. numFiles, size, err := user.ScanQuota()
  244. if err != nil {
  245. logger.Warn(logSender, "", "error scanning user quota %#v: %v", user.Username, err)
  246. return err
  247. }
  248. err = dataprovider.UpdateUserQuota(&user, numFiles, size, true)
  249. logger.Debug(logSender, "", "user quota scanned, user: %#v, error: %v", user.Username, err)
  250. return err
  251. }
  252. func doFolderQuotaScan(folder vfs.BaseVirtualFolder) error {
  253. defer common.QuotaScans.RemoveVFolderQuotaScan(folder.Name)
  254. f := vfs.VirtualFolder{
  255. BaseVirtualFolder: folder,
  256. VirtualPath: "/",
  257. }
  258. numFiles, size, err := f.ScanQuota()
  259. if err != nil {
  260. logger.Warn(logSender, "", "error scanning folder %#v: %v", folder.Name, err)
  261. return err
  262. }
  263. err = dataprovider.UpdateVirtualFolderQuota(&folder, numFiles, size, true)
  264. logger.Debug(logSender, "", "virtual folder %#v scanned, error: %v", folder.Name, err)
  265. return err
  266. }
  267. func getQuotaUpdateMode(r *http.Request) (string, error) {
  268. mode := quotaUpdateModeReset
  269. if _, ok := r.URL.Query()["mode"]; ok {
  270. mode = r.URL.Query().Get("mode")
  271. if mode != quotaUpdateModeReset && mode != quotaUpdateModeAdd {
  272. return "", errors.New("invalid mode")
  273. }
  274. }
  275. return mode, nil
  276. }