api_quota.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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. func getUsersQuotaScans(w http.ResponseWriter, r *http.Request) {
  21. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  22. render.JSON(w, r, common.QuotaScans.GetUsersQuotaScans())
  23. }
  24. func getFoldersQuotaScans(w http.ResponseWriter, r *http.Request) {
  25. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  26. render.JSON(w, r, common.QuotaScans.GetVFoldersQuotaScans())
  27. }
  28. func updateUserQuotaUsage(w http.ResponseWriter, r *http.Request) {
  29. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  30. var usage quotaUsage
  31. err := render.DecodeJSON(r.Body, &usage)
  32. if err != nil {
  33. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  34. return
  35. }
  36. doUpdateUserQuotaUsage(w, r, getURLParam(r, "username"), usage)
  37. }
  38. func updateUserQuotaUsageCompat(w http.ResponseWriter, r *http.Request) {
  39. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  40. var u dataprovider.User
  41. err := render.DecodeJSON(r.Body, &u)
  42. if err != nil {
  43. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  44. return
  45. }
  46. usage := quotaUsage{
  47. UsedQuotaSize: u.UsedQuotaSize,
  48. UsedQuotaFiles: u.UsedQuotaFiles,
  49. }
  50. doUpdateUserQuotaUsage(w, r, u.Username, usage)
  51. }
  52. func updateFolderQuotaUsage(w http.ResponseWriter, r *http.Request) {
  53. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  54. var usage quotaUsage
  55. err := render.DecodeJSON(r.Body, &usage)
  56. if err != nil {
  57. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  58. return
  59. }
  60. doUpdateFolderQuotaUsage(w, r, getURLParam(r, "name"), usage)
  61. }
  62. func updateFolderQuotaUsageCompat(w http.ResponseWriter, r *http.Request) {
  63. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  64. var f vfs.BaseVirtualFolder
  65. err := render.DecodeJSON(r.Body, &f)
  66. if err != nil {
  67. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  68. return
  69. }
  70. usage := quotaUsage{
  71. UsedQuotaSize: f.UsedQuotaSize,
  72. UsedQuotaFiles: f.UsedQuotaFiles,
  73. }
  74. doUpdateFolderQuotaUsage(w, r, f.Name, usage)
  75. }
  76. func startUserQuotaScan(w http.ResponseWriter, r *http.Request) {
  77. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  78. doStartUserQuotaScan(w, r, getURLParam(r, "username"))
  79. }
  80. func startUserQuotaScanCompat(w http.ResponseWriter, r *http.Request) {
  81. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  82. var u dataprovider.User
  83. err := render.DecodeJSON(r.Body, &u)
  84. if err != nil {
  85. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  86. return
  87. }
  88. doStartUserQuotaScan(w, r, u.Username)
  89. }
  90. func startFolderQuotaScan(w http.ResponseWriter, r *http.Request) {
  91. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  92. doStartFolderQuotaScan(w, r, getURLParam(r, "name"))
  93. }
  94. func startFolderQuotaScanCompat(w http.ResponseWriter, r *http.Request) {
  95. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  96. var f vfs.BaseVirtualFolder
  97. err := render.DecodeJSON(r.Body, &f)
  98. if err != nil {
  99. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  100. return
  101. }
  102. doStartFolderQuotaScan(w, r, f.Name)
  103. }
  104. func doUpdateUserQuotaUsage(w http.ResponseWriter, r *http.Request, username string, usage quotaUsage) {
  105. if usage.UsedQuotaFiles < 0 || usage.UsedQuotaSize < 0 {
  106. sendAPIResponse(w, r, errors.New("invalid used quota parameters, negative values are not allowed"),
  107. "", http.StatusBadRequest)
  108. return
  109. }
  110. mode, err := getQuotaUpdateMode(r)
  111. if err != nil {
  112. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  113. return
  114. }
  115. user, err := dataprovider.UserExists(username)
  116. if err != nil {
  117. sendAPIResponse(w, r, err, "", getRespStatus(err))
  118. return
  119. }
  120. if mode == quotaUpdateModeAdd && !user.HasQuotaRestrictions() && dataprovider.GetQuotaTracking() == 2 {
  121. sendAPIResponse(w, r, errors.New("this user has no quota restrictions, only reset mode is supported"),
  122. "", http.StatusBadRequest)
  123. return
  124. }
  125. if !common.QuotaScans.AddUserQuotaScan(user.Username) {
  126. sendAPIResponse(w, r, err, "A quota scan is in progress for this user", http.StatusConflict)
  127. return
  128. }
  129. defer common.QuotaScans.RemoveUserQuotaScan(user.Username)
  130. err = dataprovider.UpdateUserQuota(&user, usage.UsedQuotaFiles, usage.UsedQuotaSize, mode == quotaUpdateModeReset)
  131. if err != nil {
  132. sendAPIResponse(w, r, err, "", getRespStatus(err))
  133. } else {
  134. sendAPIResponse(w, r, err, "Quota updated", http.StatusOK)
  135. }
  136. }
  137. func doUpdateFolderQuotaUsage(w http.ResponseWriter, r *http.Request, name string, usage quotaUsage) {
  138. if usage.UsedQuotaFiles < 0 || usage.UsedQuotaSize < 0 {
  139. sendAPIResponse(w, r, errors.New("invalid used quota parameters, negative values are not allowed"),
  140. "", http.StatusBadRequest)
  141. return
  142. }
  143. mode, err := getQuotaUpdateMode(r)
  144. if err != nil {
  145. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  146. return
  147. }
  148. folder, err := dataprovider.GetFolderByName(name)
  149. if err != nil {
  150. sendAPIResponse(w, r, err, "", getRespStatus(err))
  151. return
  152. }
  153. if !common.QuotaScans.AddVFolderQuotaScan(folder.Name) {
  154. sendAPIResponse(w, r, err, "A quota scan is in progress for this folder", http.StatusConflict)
  155. return
  156. }
  157. defer common.QuotaScans.RemoveVFolderQuotaScan(folder.Name)
  158. err = dataprovider.UpdateVirtualFolderQuota(&folder, usage.UsedQuotaFiles, usage.UsedQuotaSize, mode == quotaUpdateModeReset)
  159. if err != nil {
  160. sendAPIResponse(w, r, err, "", getRespStatus(err))
  161. } else {
  162. sendAPIResponse(w, r, err, "Quota updated", http.StatusOK)
  163. }
  164. }
  165. func doStartUserQuotaScan(w http.ResponseWriter, r *http.Request, username string) {
  166. if dataprovider.GetQuotaTracking() == 0 {
  167. sendAPIResponse(w, r, nil, "Quota tracking is disabled!", http.StatusForbidden)
  168. return
  169. }
  170. user, err := dataprovider.UserExists(username)
  171. if err != nil {
  172. sendAPIResponse(w, r, err, "", getRespStatus(err))
  173. return
  174. }
  175. if !common.QuotaScans.AddUserQuotaScan(user.Username) {
  176. sendAPIResponse(w, r, err, fmt.Sprintf("Another scan is already in progress for user %#v", username),
  177. http.StatusConflict)
  178. return
  179. }
  180. go doUserQuotaScan(user) //nolint:errcheck
  181. sendAPIResponse(w, r, err, "Scan started", http.StatusAccepted)
  182. }
  183. func doStartFolderQuotaScan(w http.ResponseWriter, r *http.Request, name string) {
  184. if dataprovider.GetQuotaTracking() == 0 {
  185. sendAPIResponse(w, r, nil, "Quota tracking is disabled!", http.StatusForbidden)
  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, fmt.Sprintf("Another scan is already in progress for folder %#v", name),
  195. http.StatusConflict)
  196. return
  197. }
  198. go doFolderQuotaScan(folder) //nolint:errcheck
  199. sendAPIResponse(w, r, err, "Scan started", http.StatusAccepted)
  200. }
  201. func doUserQuotaScan(user dataprovider.User) error {
  202. defer common.QuotaScans.RemoveUserQuotaScan(user.Username)
  203. numFiles, size, err := user.ScanQuota()
  204. if err != nil {
  205. logger.Warn(logSender, "", "error scanning user quota %#v: %v", user.Username, err)
  206. return err
  207. }
  208. err = dataprovider.UpdateUserQuota(&user, numFiles, size, true)
  209. logger.Debug(logSender, "", "user quota scanned, user: %#v, error: %v", user.Username, err)
  210. return err
  211. }
  212. func doFolderQuotaScan(folder vfs.BaseVirtualFolder) error {
  213. defer common.QuotaScans.RemoveVFolderQuotaScan(folder.Name)
  214. f := vfs.VirtualFolder{
  215. BaseVirtualFolder: folder,
  216. VirtualPath: "/",
  217. }
  218. numFiles, size, err := f.ScanQuota()
  219. if err != nil {
  220. logger.Warn(logSender, "", "error scanning folder %#v: %v", folder.Name, err)
  221. return err
  222. }
  223. err = dataprovider.UpdateVirtualFolderQuota(&folder, numFiles, size, true)
  224. logger.Debug(logSender, "", "virtual folder %#v scanned, error: %v", folder.Name, err)
  225. return err
  226. }
  227. func getQuotaUpdateMode(r *http.Request) (string, error) {
  228. mode := quotaUpdateModeReset
  229. if _, ok := r.URL.Query()["mode"]; ok {
  230. mode = r.URL.Query().Get("mode")
  231. if mode != quotaUpdateModeReset && mode != quotaUpdateModeAdd {
  232. return "", errors.New("invalid mode")
  233. }
  234. }
  235. return mode, nil
  236. }