api_user.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. package httpd
  2. import (
  3. "errors"
  4. "fmt"
  5. "net/http"
  6. "strconv"
  7. "github.com/go-chi/chi"
  8. "github.com/go-chi/render"
  9. "github.com/drakkan/sftpgo/common"
  10. "github.com/drakkan/sftpgo/dataprovider"
  11. "github.com/drakkan/sftpgo/vfs"
  12. )
  13. func getUsers(w http.ResponseWriter, r *http.Request) {
  14. limit := 100
  15. offset := 0
  16. order := dataprovider.OrderASC
  17. username := ""
  18. var err error
  19. if _, ok := r.URL.Query()["limit"]; ok {
  20. limit, err = strconv.Atoi(r.URL.Query().Get("limit"))
  21. if err != nil {
  22. err = errors.New("Invalid limit")
  23. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  24. return
  25. }
  26. if limit > 500 {
  27. limit = 500
  28. }
  29. }
  30. if _, ok := r.URL.Query()["offset"]; ok {
  31. offset, err = strconv.Atoi(r.URL.Query().Get("offset"))
  32. if err != nil {
  33. err = errors.New("Invalid offset")
  34. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  35. return
  36. }
  37. }
  38. if _, ok := r.URL.Query()["order"]; ok {
  39. order = r.URL.Query().Get("order")
  40. if order != dataprovider.OrderASC && order != dataprovider.OrderDESC {
  41. err = errors.New("Invalid order")
  42. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  43. return
  44. }
  45. }
  46. if _, ok := r.URL.Query()["username"]; ok {
  47. username = r.URL.Query().Get("username")
  48. }
  49. users, err := dataprovider.GetUsers(limit, offset, order, username)
  50. if err == nil {
  51. render.JSON(w, r, users)
  52. } else {
  53. sendAPIResponse(w, r, err, "", http.StatusInternalServerError)
  54. }
  55. }
  56. func getUserByID(w http.ResponseWriter, r *http.Request) {
  57. userID, err := strconv.ParseInt(chi.URLParam(r, "userID"), 10, 64)
  58. if err != nil {
  59. err = errors.New("Invalid userID")
  60. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  61. return
  62. }
  63. user, err := dataprovider.GetUserByID(userID)
  64. if err == nil {
  65. user.HideConfidentialData()
  66. render.JSON(w, r, user)
  67. } else {
  68. sendAPIResponse(w, r, err, "", getRespStatus(err))
  69. }
  70. }
  71. func addUser(w http.ResponseWriter, r *http.Request) {
  72. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  73. var user dataprovider.User
  74. err := render.DecodeJSON(r.Body, &user)
  75. if err != nil {
  76. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  77. return
  78. }
  79. switch user.FsConfig.Provider {
  80. case dataprovider.S3FilesystemProvider:
  81. if user.FsConfig.S3Config.AccessSecret.IsRedacted() {
  82. sendAPIResponse(w, r, errors.New("invalid access_secret"), "", http.StatusBadRequest)
  83. return
  84. }
  85. case dataprovider.GCSFilesystemProvider:
  86. if user.FsConfig.GCSConfig.Credentials.IsRedacted() {
  87. sendAPIResponse(w, r, errors.New("invalid credentials"), "", http.StatusBadRequest)
  88. return
  89. }
  90. case dataprovider.AzureBlobFilesystemProvider:
  91. if user.FsConfig.AzBlobConfig.AccountKey.IsRedacted() {
  92. sendAPIResponse(w, r, errors.New("invalid account_key"), "", http.StatusBadRequest)
  93. return
  94. }
  95. }
  96. err = dataprovider.AddUser(user)
  97. if err == nil {
  98. user, err = dataprovider.UserExists(user.Username)
  99. if err == nil {
  100. user.HideConfidentialData()
  101. render.JSON(w, r, user)
  102. } else {
  103. sendAPIResponse(w, r, err, "", getRespStatus(err))
  104. }
  105. } else {
  106. sendAPIResponse(w, r, err, "", getRespStatus(err))
  107. }
  108. }
  109. func updateUser(w http.ResponseWriter, r *http.Request) {
  110. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  111. userID, err := strconv.ParseInt(chi.URLParam(r, "userID"), 10, 64)
  112. if err != nil {
  113. err = errors.New("Invalid userID")
  114. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  115. return
  116. }
  117. disconnect := 0
  118. if _, ok := r.URL.Query()["disconnect"]; ok {
  119. disconnect, err = strconv.Atoi(r.URL.Query().Get("disconnect"))
  120. if err != nil {
  121. err = fmt.Errorf("invalid disconnect parameter: %v", err)
  122. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  123. return
  124. }
  125. }
  126. user, err := dataprovider.GetUserByID(userID)
  127. if err != nil {
  128. sendAPIResponse(w, r, err, "", getRespStatus(err))
  129. return
  130. }
  131. currentPermissions := user.Permissions
  132. var currentS3AccessSecret vfs.Secret
  133. var currentAzAccountKey vfs.Secret
  134. var currentGCSCredentials vfs.Secret
  135. if user.FsConfig.Provider == dataprovider.S3FilesystemProvider {
  136. currentS3AccessSecret = user.FsConfig.S3Config.AccessSecret
  137. }
  138. if user.FsConfig.Provider == dataprovider.AzureBlobFilesystemProvider {
  139. currentAzAccountKey = user.FsConfig.AzBlobConfig.AccountKey
  140. }
  141. if user.FsConfig.Provider == dataprovider.GCSFilesystemProvider {
  142. currentGCSCredentials = user.FsConfig.GCSConfig.Credentials
  143. }
  144. user.Permissions = make(map[string][]string)
  145. user.FsConfig.S3Config = vfs.S3FsConfig{}
  146. user.FsConfig.AzBlobConfig = vfs.AzBlobFsConfig{}
  147. user.FsConfig.GCSConfig = vfs.GCSFsConfig{}
  148. err = render.DecodeJSON(r.Body, &user)
  149. if err != nil {
  150. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  151. return
  152. }
  153. // we use new Permissions if passed otherwise the old ones
  154. if len(user.Permissions) == 0 {
  155. user.Permissions = currentPermissions
  156. }
  157. updateEncryptedSecrets(&user, currentS3AccessSecret, currentAzAccountKey, currentGCSCredentials)
  158. if user.ID != userID {
  159. sendAPIResponse(w, r, err, "user ID in request body does not match user ID in path parameter", http.StatusBadRequest)
  160. return
  161. }
  162. err = dataprovider.UpdateUser(user)
  163. if err != nil {
  164. sendAPIResponse(w, r, err, "", getRespStatus(err))
  165. } else {
  166. sendAPIResponse(w, r, err, "User updated", http.StatusOK)
  167. if disconnect == 1 {
  168. disconnectUser(user.Username)
  169. }
  170. }
  171. }
  172. func deleteUser(w http.ResponseWriter, r *http.Request) {
  173. userID, err := strconv.ParseInt(chi.URLParam(r, "userID"), 10, 64)
  174. if err != nil {
  175. err = errors.New("Invalid userID")
  176. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  177. return
  178. }
  179. user, err := dataprovider.GetUserByID(userID)
  180. if err != nil {
  181. sendAPIResponse(w, r, err, "", getRespStatus(err))
  182. return
  183. }
  184. err = dataprovider.DeleteUser(user)
  185. if err != nil {
  186. sendAPIResponse(w, r, err, "", http.StatusInternalServerError)
  187. } else {
  188. sendAPIResponse(w, r, err, "User deleted", http.StatusOK)
  189. disconnectUser(user.Username)
  190. }
  191. }
  192. func disconnectUser(username string) {
  193. for _, stat := range common.Connections.GetStats() {
  194. if stat.Username == username {
  195. common.Connections.Close(stat.ConnectionID)
  196. }
  197. }
  198. }
  199. func updateEncryptedSecrets(user *dataprovider.User, currentS3AccessSecret, currentAzAccountKey, currentGCSCredentials vfs.Secret) {
  200. // we use the new access secret if plain or empty, otherwise the old value
  201. if user.FsConfig.Provider == dataprovider.S3FilesystemProvider {
  202. if !user.FsConfig.S3Config.AccessSecret.IsPlain() && !user.FsConfig.S3Config.AccessSecret.IsEmpty() {
  203. user.FsConfig.S3Config.AccessSecret = currentS3AccessSecret
  204. }
  205. }
  206. if user.FsConfig.Provider == dataprovider.AzureBlobFilesystemProvider {
  207. if !user.FsConfig.AzBlobConfig.AccountKey.IsPlain() && !user.FsConfig.AzBlobConfig.AccountKey.IsEmpty() {
  208. user.FsConfig.AzBlobConfig.AccountKey = currentAzAccountKey
  209. }
  210. }
  211. if user.FsConfig.Provider == dataprovider.GCSFilesystemProvider {
  212. if !user.FsConfig.GCSConfig.Credentials.IsPlain() && !user.FsConfig.GCSConfig.Credentials.IsEmpty() {
  213. user.FsConfig.GCSConfig.Credentials = currentGCSCredentials
  214. }
  215. }
  216. }