api_quota.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. // Copyright (C) 2019-2022 Nicola Murino
  2. //
  3. // This program is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU Affero General Public License as published
  5. // by the Free Software Foundation, version 3.
  6. //
  7. // This program is distributed in the hope that it will be useful,
  8. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. // GNU Affero General Public License for more details.
  11. //
  12. // You should have received a copy of the GNU Affero General Public License
  13. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  14. package httpd
  15. import (
  16. "errors"
  17. "fmt"
  18. "net/http"
  19. "github.com/go-chi/render"
  20. "github.com/drakkan/sftpgo/v2/internal/common"
  21. "github.com/drakkan/sftpgo/v2/internal/dataprovider"
  22. "github.com/drakkan/sftpgo/v2/internal/logger"
  23. "github.com/drakkan/sftpgo/v2/internal/vfs"
  24. )
  25. const (
  26. quotaUpdateModeAdd = "add"
  27. quotaUpdateModeReset = "reset"
  28. )
  29. type quotaUsage struct {
  30. UsedQuotaSize int64 `json:"used_quota_size"`
  31. UsedQuotaFiles int `json:"used_quota_files"`
  32. }
  33. type transferQuotaUsage struct {
  34. UsedUploadDataTransfer int64 `json:"used_upload_data_transfer"`
  35. UsedDownloadDataTransfer int64 `json:"used_download_data_transfer"`
  36. }
  37. func getUsersQuotaScans(w http.ResponseWriter, r *http.Request) {
  38. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  39. render.JSON(w, r, common.QuotaScans.GetUsersQuotaScans())
  40. }
  41. func getFoldersQuotaScans(w http.ResponseWriter, r *http.Request) {
  42. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  43. render.JSON(w, r, common.QuotaScans.GetVFoldersQuotaScans())
  44. }
  45. func updateUserQuotaUsage(w http.ResponseWriter, r *http.Request) {
  46. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  47. var usage quotaUsage
  48. err := render.DecodeJSON(r.Body, &usage)
  49. if err != nil {
  50. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  51. return
  52. }
  53. doUpdateUserQuotaUsage(w, r, getURLParam(r, "username"), usage)
  54. }
  55. func updateFolderQuotaUsage(w http.ResponseWriter, r *http.Request) {
  56. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  57. var usage quotaUsage
  58. err := render.DecodeJSON(r.Body, &usage)
  59. if err != nil {
  60. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  61. return
  62. }
  63. doUpdateFolderQuotaUsage(w, r, getURLParam(r, "name"), usage)
  64. }
  65. func startUserQuotaScan(w http.ResponseWriter, r *http.Request) {
  66. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  67. doStartUserQuotaScan(w, r, getURLParam(r, "username"))
  68. }
  69. func startFolderQuotaScan(w http.ResponseWriter, r *http.Request) {
  70. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  71. doStartFolderQuotaScan(w, r, getURLParam(r, "name"))
  72. }
  73. func updateUserTransferQuotaUsage(w http.ResponseWriter, r *http.Request) {
  74. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  75. var usage transferQuotaUsage
  76. err := render.DecodeJSON(r.Body, &usage)
  77. if err != nil {
  78. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  79. return
  80. }
  81. if usage.UsedUploadDataTransfer < 0 || usage.UsedDownloadDataTransfer < 0 {
  82. sendAPIResponse(w, r, errors.New("invalid used transfer quota parameters, negative values are not allowed"),
  83. "", http.StatusBadRequest)
  84. return
  85. }
  86. mode, err := getQuotaUpdateMode(r)
  87. if err != nil {
  88. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  89. return
  90. }
  91. user, err := dataprovider.GetUserWithGroupSettings(getURLParam(r, "username"))
  92. if err != nil {
  93. sendAPIResponse(w, r, err, "", getRespStatus(err))
  94. return
  95. }
  96. if mode == quotaUpdateModeAdd && !user.HasTransferQuotaRestrictions() && dataprovider.GetQuotaTracking() == 2 {
  97. sendAPIResponse(w, r, errors.New("this user has no transfer quota restrictions, only reset mode is supported"),
  98. "", http.StatusBadRequest)
  99. return
  100. }
  101. err = dataprovider.UpdateUserTransferQuota(&user, usage.UsedUploadDataTransfer, usage.UsedDownloadDataTransfer,
  102. mode == quotaUpdateModeReset)
  103. if err != nil {
  104. sendAPIResponse(w, r, err, "", getRespStatus(err))
  105. return
  106. }
  107. sendAPIResponse(w, r, err, "Quota updated", http.StatusOK)
  108. }
  109. func doUpdateUserQuotaUsage(w http.ResponseWriter, r *http.Request, username string, usage quotaUsage) {
  110. if usage.UsedQuotaFiles < 0 || usage.UsedQuotaSize < 0 {
  111. sendAPIResponse(w, r, errors.New("invalid used quota parameters, negative values are not allowed"),
  112. "", http.StatusBadRequest)
  113. return
  114. }
  115. mode, err := getQuotaUpdateMode(r)
  116. if err != nil {
  117. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  118. return
  119. }
  120. user, err := dataprovider.GetUserWithGroupSettings(username)
  121. if err != nil {
  122. sendAPIResponse(w, r, err, "", getRespStatus(err))
  123. return
  124. }
  125. if mode == quotaUpdateModeAdd && !user.HasQuotaRestrictions() && dataprovider.GetQuotaTracking() == 2 {
  126. sendAPIResponse(w, r, errors.New("this user has no quota restrictions, only reset mode is supported"),
  127. "", http.StatusBadRequest)
  128. return
  129. }
  130. if !common.QuotaScans.AddUserQuotaScan(user.Username) {
  131. sendAPIResponse(w, r, err, "A quota scan is in progress for this user", http.StatusConflict)
  132. return
  133. }
  134. defer common.QuotaScans.RemoveUserQuotaScan(user.Username)
  135. err = dataprovider.UpdateUserQuota(&user, usage.UsedQuotaFiles, usage.UsedQuotaSize, mode == quotaUpdateModeReset)
  136. if err != nil {
  137. sendAPIResponse(w, r, err, "", getRespStatus(err))
  138. return
  139. }
  140. sendAPIResponse(w, r, err, "Quota updated", http.StatusOK)
  141. }
  142. func doUpdateFolderQuotaUsage(w http.ResponseWriter, r *http.Request, name string, usage quotaUsage) {
  143. if usage.UsedQuotaFiles < 0 || usage.UsedQuotaSize < 0 {
  144. sendAPIResponse(w, r, errors.New("invalid used quota parameters, negative values are not allowed"),
  145. "", http.StatusBadRequest)
  146. return
  147. }
  148. mode, err := getQuotaUpdateMode(r)
  149. if err != nil {
  150. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  151. return
  152. }
  153. folder, err := dataprovider.GetFolderByName(name)
  154. if err != nil {
  155. sendAPIResponse(w, r, err, "", getRespStatus(err))
  156. return
  157. }
  158. if !common.QuotaScans.AddVFolderQuotaScan(folder.Name) {
  159. sendAPIResponse(w, r, err, "A quota scan is in progress for this folder", http.StatusConflict)
  160. return
  161. }
  162. defer common.QuotaScans.RemoveVFolderQuotaScan(folder.Name)
  163. err = dataprovider.UpdateVirtualFolderQuota(&folder, usage.UsedQuotaFiles, usage.UsedQuotaSize, mode == quotaUpdateModeReset)
  164. if err != nil {
  165. sendAPIResponse(w, r, err, "", getRespStatus(err))
  166. } else {
  167. sendAPIResponse(w, r, err, "Quota updated", http.StatusOK)
  168. }
  169. }
  170. func doStartUserQuotaScan(w http.ResponseWriter, r *http.Request, username string) {
  171. if dataprovider.GetQuotaTracking() == 0 {
  172. sendAPIResponse(w, r, nil, "Quota tracking is disabled!", http.StatusForbidden)
  173. return
  174. }
  175. user, err := dataprovider.GetUserWithGroupSettings(username)
  176. if err != nil {
  177. sendAPIResponse(w, r, err, "", getRespStatus(err))
  178. return
  179. }
  180. if !common.QuotaScans.AddUserQuotaScan(user.Username) {
  181. sendAPIResponse(w, r, nil, fmt.Sprintf("Another scan is already in progress for user %#v", username),
  182. http.StatusConflict)
  183. return
  184. }
  185. go doUserQuotaScan(user) //nolint:errcheck
  186. sendAPIResponse(w, r, err, "Scan started", http.StatusAccepted)
  187. }
  188. func doStartFolderQuotaScan(w http.ResponseWriter, r *http.Request, name string) {
  189. if dataprovider.GetQuotaTracking() == 0 {
  190. sendAPIResponse(w, r, nil, "Quota tracking is disabled!", http.StatusForbidden)
  191. return
  192. }
  193. folder, err := dataprovider.GetFolderByName(name)
  194. if err != nil {
  195. sendAPIResponse(w, r, err, "", getRespStatus(err))
  196. return
  197. }
  198. if !common.QuotaScans.AddVFolderQuotaScan(folder.Name) {
  199. sendAPIResponse(w, r, err, fmt.Sprintf("Another scan is already in progress for folder %#v", name),
  200. http.StatusConflict)
  201. return
  202. }
  203. go doFolderQuotaScan(folder) //nolint:errcheck
  204. sendAPIResponse(w, r, err, "Scan started", http.StatusAccepted)
  205. }
  206. func doUserQuotaScan(user dataprovider.User) error {
  207. defer common.QuotaScans.RemoveUserQuotaScan(user.Username)
  208. numFiles, size, err := user.ScanQuota()
  209. if err != nil {
  210. logger.Warn(logSender, "", "error scanning user quota %#v: %v", user.Username, err)
  211. return err
  212. }
  213. err = dataprovider.UpdateUserQuota(&user, numFiles, size, true)
  214. logger.Debug(logSender, "", "user quota scanned, user: %#v, error: %v", user.Username, err)
  215. return err
  216. }
  217. func doFolderQuotaScan(folder vfs.BaseVirtualFolder) error {
  218. defer common.QuotaScans.RemoveVFolderQuotaScan(folder.Name)
  219. f := vfs.VirtualFolder{
  220. BaseVirtualFolder: folder,
  221. VirtualPath: "/",
  222. }
  223. numFiles, size, err := f.ScanQuota()
  224. if err != nil {
  225. logger.Warn(logSender, "", "error scanning folder %#v: %v", folder.Name, err)
  226. return err
  227. }
  228. err = dataprovider.UpdateVirtualFolderQuota(&folder, numFiles, size, true)
  229. logger.Debug(logSender, "", "virtual folder %#v scanned, error: %v", folder.Name, err)
  230. return err
  231. }
  232. func getQuotaUpdateMode(r *http.Request) (string, error) {
  233. mode := quotaUpdateModeReset
  234. if _, ok := r.URL.Query()["mode"]; ok {
  235. mode = r.URL.Query().Get("mode")
  236. if mode != quotaUpdateModeReset && mode != quotaUpdateModeAdd {
  237. return "", errors.New("invalid mode")
  238. }
  239. }
  240. return mode, nil
  241. }