web.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. package httpd
  2. import (
  3. "fmt"
  4. "html/template"
  5. "net/http"
  6. "path/filepath"
  7. "strconv"
  8. "strings"
  9. "time"
  10. "github.com/drakkan/sftpgo/dataprovider"
  11. "github.com/drakkan/sftpgo/sftpd"
  12. "github.com/drakkan/sftpgo/utils"
  13. )
  14. const (
  15. templateBase = "base.html"
  16. templateUsers = "users.html"
  17. templateUser = "user.html"
  18. templateConnections = "connections.html"
  19. templateMessage = "message.html"
  20. pageUsersTitle = "Users"
  21. pageConnectionsTitle = "Connections"
  22. page400Title = "Bad request"
  23. page404Title = "Not found"
  24. page404Body = "The page you are looking for does not exist."
  25. page500Title = "Internal Server Error"
  26. page500Body = "The server is unable to fulfill your request."
  27. defaultUsersQueryLimit = 500
  28. webDateTimeFormat = "2006-01-02 15:04:05" // YYYY-MM-DD HH:MM:SS
  29. )
  30. var (
  31. templates = make(map[string]*template.Template)
  32. )
  33. type basePage struct {
  34. Title string
  35. CurrentURL string
  36. UsersURL string
  37. UserURL string
  38. APIUserURL string
  39. APIConnectionsURL string
  40. APIQuotaScanURL string
  41. ConnectionsURL string
  42. UsersTitle string
  43. ConnectionsTitle string
  44. Version string
  45. }
  46. type usersPage struct {
  47. basePage
  48. Users []dataprovider.User
  49. }
  50. type connectionsPage struct {
  51. basePage
  52. Connections []sftpd.ConnectionStatus
  53. }
  54. type userPage struct {
  55. basePage
  56. IsAdd bool
  57. User dataprovider.User
  58. RootPerms []string
  59. Error string
  60. ValidPerms []string
  61. RootDirPerms []string
  62. }
  63. type messagePage struct {
  64. basePage
  65. Error string
  66. Success string
  67. }
  68. func loadTemplates(templatesPath string) {
  69. usersPaths := []string{
  70. filepath.Join(templatesPath, templateBase),
  71. filepath.Join(templatesPath, templateUsers),
  72. }
  73. userPaths := []string{
  74. filepath.Join(templatesPath, templateBase),
  75. filepath.Join(templatesPath, templateUser),
  76. }
  77. connectionsPaths := []string{
  78. filepath.Join(templatesPath, templateBase),
  79. filepath.Join(templatesPath, templateConnections),
  80. }
  81. messagePath := []string{
  82. filepath.Join(templatesPath, templateBase),
  83. filepath.Join(templatesPath, templateMessage),
  84. }
  85. usersTmpl := template.Must(template.ParseFiles(usersPaths...))
  86. userTmpl := template.Must(template.ParseFiles(userPaths...))
  87. connectionsTmpl := template.Must(template.ParseFiles(connectionsPaths...))
  88. messageTmpl := template.Must(template.ParseFiles(messagePath...))
  89. templates[templateUsers] = usersTmpl
  90. templates[templateUser] = userTmpl
  91. templates[templateConnections] = connectionsTmpl
  92. templates[templateMessage] = messageTmpl
  93. }
  94. func getBasePageData(title, currentURL string) basePage {
  95. version := utils.GetAppVersion()
  96. return basePage{
  97. Title: title,
  98. CurrentURL: currentURL,
  99. UsersURL: webUsersPath,
  100. UserURL: webUserPath,
  101. APIUserURL: userPath,
  102. APIConnectionsURL: activeConnectionsPath,
  103. APIQuotaScanURL: quotaScanPath,
  104. ConnectionsURL: webConnectionsPath,
  105. UsersTitle: pageUsersTitle,
  106. ConnectionsTitle: pageConnectionsTitle,
  107. Version: version.GetVersionAsString(),
  108. }
  109. }
  110. func renderTemplate(w http.ResponseWriter, tmplName string, data interface{}) {
  111. err := templates[tmplName].ExecuteTemplate(w, tmplName, data)
  112. if err != nil {
  113. http.Error(w, err.Error(), http.StatusInternalServerError)
  114. }
  115. }
  116. func renderMessagePage(w http.ResponseWriter, title, body string, statusCode int, err error, message string) {
  117. var errorString string
  118. if len(body) > 0 {
  119. errorString = body + " "
  120. }
  121. if err != nil {
  122. errorString += err.Error()
  123. }
  124. data := messagePage{
  125. basePage: getBasePageData(title, ""),
  126. Error: errorString,
  127. Success: message,
  128. }
  129. w.WriteHeader(statusCode)
  130. renderTemplate(w, templateMessage, data)
  131. }
  132. func renderInternalServerErrorPage(w http.ResponseWriter, err error) {
  133. renderMessagePage(w, page500Title, page400Title, http.StatusInternalServerError, err, "")
  134. }
  135. func renderBadRequestPage(w http.ResponseWriter, err error) {
  136. renderMessagePage(w, page400Title, "", http.StatusBadRequest, err, "")
  137. }
  138. func renderNotFoundPage(w http.ResponseWriter, err error) {
  139. renderMessagePage(w, page404Title, page404Body, http.StatusNotFound, err, "")
  140. }
  141. func renderAddUserPage(w http.ResponseWriter, user dataprovider.User, error string) {
  142. data := userPage{
  143. basePage: getBasePageData("Add a new user", webUserPath),
  144. IsAdd: true,
  145. Error: error,
  146. User: user,
  147. ValidPerms: dataprovider.ValidPerms,
  148. RootDirPerms: user.GetPermissionsForPath("/"),
  149. }
  150. renderTemplate(w, templateUser, data)
  151. }
  152. func renderUpdateUserPage(w http.ResponseWriter, user dataprovider.User, error string) {
  153. data := userPage{
  154. basePage: getBasePageData("Update user", fmt.Sprintf("%v/%v", webUserPath, user.ID)),
  155. IsAdd: false,
  156. Error: error,
  157. User: user,
  158. ValidPerms: dataprovider.ValidPerms,
  159. RootDirPerms: user.GetPermissionsForPath("/"),
  160. }
  161. renderTemplate(w, templateUser, data)
  162. }
  163. func getUserPermissionsFromPostFields(r *http.Request) map[string][]string {
  164. permissions := make(map[string][]string)
  165. permissions["/"] = r.Form["permissions"]
  166. subDirsPermsValue := r.Form.Get("sub_dirs_permissions")
  167. for _, cleaned := range getSliceFromDelimitedValues(subDirsPermsValue, "\n") {
  168. if strings.ContainsRune(cleaned, ':') {
  169. dirPerms := strings.Split(cleaned, ":")
  170. if len(dirPerms) > 1 {
  171. dir := dirPerms[0]
  172. dir = strings.TrimSpace(dir)
  173. perms := []string{}
  174. for _, p := range strings.Split(dirPerms[1], ",") {
  175. cleanedPerm := strings.TrimSpace(p)
  176. if len(cleanedPerm) > 0 {
  177. perms = append(perms, cleanedPerm)
  178. }
  179. }
  180. if len(dir) > 0 && len(perms) > 0 {
  181. permissions[dir] = perms
  182. }
  183. }
  184. }
  185. }
  186. return permissions
  187. }
  188. func getSliceFromDelimitedValues(values, delimiter string) []string {
  189. result := []string{}
  190. for _, v := range strings.Split(values, delimiter) {
  191. cleaned := strings.TrimSpace(v)
  192. if len(cleaned) > 0 {
  193. result = append(result, cleaned)
  194. }
  195. }
  196. return result
  197. }
  198. func getFiltersFromUserPostFields(r *http.Request) dataprovider.UserFilters {
  199. var filters dataprovider.UserFilters
  200. filters.AllowedIP = getSliceFromDelimitedValues(r.Form.Get("allowed_ip"), ",")
  201. filters.DeniedIP = getSliceFromDelimitedValues(r.Form.Get("denied_ip"), ",")
  202. return filters
  203. }
  204. func getFsConfigFromUserPostFields(r *http.Request) dataprovider.Filesystem {
  205. var fs dataprovider.Filesystem
  206. provider, err := strconv.Atoi(r.Form.Get("fs_provider"))
  207. if err != nil {
  208. provider = 0
  209. }
  210. fs.Provider = provider
  211. if fs.Provider == 1 {
  212. fs.S3Config.Bucket = r.Form.Get("s3_bucket")
  213. fs.S3Config.Region = r.Form.Get("s3_region")
  214. fs.S3Config.AccessKey = r.Form.Get("s3_access_key")
  215. fs.S3Config.AccessSecret = r.Form.Get("s3_access_secret")
  216. fs.S3Config.Endpoint = r.Form.Get("s3_endpoint")
  217. fs.S3Config.StorageClass = r.Form.Get("s3_storage_class")
  218. fs.S3Config.KeyPrefix = r.Form.Get("s3_key_prefix")
  219. }
  220. return fs
  221. }
  222. func getUserFromPostFields(r *http.Request) (dataprovider.User, error) {
  223. var user dataprovider.User
  224. err := r.ParseForm()
  225. if err != nil {
  226. return user, err
  227. }
  228. publicKeysFormValue := r.Form.Get("public_keys")
  229. publicKeys := getSliceFromDelimitedValues(publicKeysFormValue, "\n")
  230. uid, err := strconv.Atoi(r.Form.Get("uid"))
  231. if err != nil {
  232. return user, err
  233. }
  234. gid, err := strconv.Atoi(r.Form.Get("gid"))
  235. if err != nil {
  236. return user, err
  237. }
  238. maxSessions, err := strconv.Atoi(r.Form.Get("max_sessions"))
  239. if err != nil {
  240. return user, err
  241. }
  242. quotaSize, err := strconv.ParseInt(r.Form.Get("quota_size"), 10, 64)
  243. if err != nil {
  244. return user, err
  245. }
  246. quotaFiles, err := strconv.Atoi(r.Form.Get("quota_files"))
  247. if err != nil {
  248. return user, err
  249. }
  250. bandwidthUL, err := strconv.ParseInt(r.Form.Get("upload_bandwidth"), 10, 64)
  251. if err != nil {
  252. return user, err
  253. }
  254. bandwidthDL, err := strconv.ParseInt(r.Form.Get("download_bandwidth"), 10, 64)
  255. if err != nil {
  256. return user, err
  257. }
  258. status, err := strconv.Atoi(r.Form.Get("status"))
  259. if err != nil {
  260. return user, err
  261. }
  262. expirationDateMillis := int64(0)
  263. expirationDateString := r.Form.Get("expiration_date")
  264. if len(strings.TrimSpace(expirationDateString)) > 0 {
  265. expirationDate, err := time.Parse(webDateTimeFormat, expirationDateString)
  266. if err != nil {
  267. return user, err
  268. }
  269. expirationDateMillis = utils.GetTimeAsMsSinceEpoch(expirationDate)
  270. }
  271. user = dataprovider.User{
  272. Username: r.Form.Get("username"),
  273. Password: r.Form.Get("password"),
  274. PublicKeys: publicKeys,
  275. HomeDir: r.Form.Get("home_dir"),
  276. UID: uid,
  277. GID: gid,
  278. Permissions: getUserPermissionsFromPostFields(r),
  279. MaxSessions: maxSessions,
  280. QuotaSize: quotaSize,
  281. QuotaFiles: quotaFiles,
  282. UploadBandwidth: bandwidthUL,
  283. DownloadBandwidth: bandwidthDL,
  284. Status: status,
  285. ExpirationDate: expirationDateMillis,
  286. Filters: getFiltersFromUserPostFields(r),
  287. FsConfig: getFsConfigFromUserPostFields(r),
  288. }
  289. return user, err
  290. }
  291. func handleGetWebUsers(w http.ResponseWriter, r *http.Request) {
  292. limit := defaultUsersQueryLimit
  293. if _, ok := r.URL.Query()["qlimit"]; ok {
  294. var err error
  295. limit, err = strconv.Atoi(r.URL.Query().Get("qlimit"))
  296. if err != nil {
  297. limit = defaultUsersQueryLimit
  298. }
  299. }
  300. var users []dataprovider.User
  301. u, err := dataprovider.GetUsers(dataProvider, limit, 0, "ASC", "")
  302. users = append(users, u...)
  303. for len(u) == limit {
  304. u, err = dataprovider.GetUsers(dataProvider, limit, len(users), "ASC", "")
  305. if err == nil && len(u) > 0 {
  306. users = append(users, u...)
  307. } else {
  308. break
  309. }
  310. }
  311. if err != nil {
  312. renderInternalServerErrorPage(w, err)
  313. return
  314. }
  315. data := usersPage{
  316. basePage: getBasePageData(pageUsersTitle, webUsersPath),
  317. Users: users,
  318. }
  319. renderTemplate(w, templateUsers, data)
  320. }
  321. func handleWebAddUserGet(w http.ResponseWriter, r *http.Request) {
  322. renderAddUserPage(w, dataprovider.User{Status: 1}, "")
  323. }
  324. func handleWebUpdateUserGet(userID string, w http.ResponseWriter, r *http.Request) {
  325. id, err := strconv.ParseInt(userID, 10, 64)
  326. if err != nil {
  327. renderBadRequestPage(w, err)
  328. return
  329. }
  330. user, err := dataprovider.GetUserByID(dataProvider, id)
  331. if err == nil {
  332. renderUpdateUserPage(w, user, "")
  333. } else if _, ok := err.(*dataprovider.RecordNotFoundError); ok {
  334. renderNotFoundPage(w, err)
  335. } else {
  336. renderInternalServerErrorPage(w, err)
  337. }
  338. }
  339. func handleWebAddUserPost(w http.ResponseWriter, r *http.Request) {
  340. user, err := getUserFromPostFields(r)
  341. if err != nil {
  342. renderAddUserPage(w, user, err.Error())
  343. return
  344. }
  345. err = dataprovider.AddUser(dataProvider, user)
  346. if err == nil {
  347. http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
  348. } else {
  349. renderAddUserPage(w, user, err.Error())
  350. }
  351. }
  352. func handleWebUpdateUserPost(userID string, w http.ResponseWriter, r *http.Request) {
  353. id, err := strconv.ParseInt(userID, 10, 64)
  354. if err != nil {
  355. renderBadRequestPage(w, err)
  356. return
  357. }
  358. user, err := dataprovider.GetUserByID(dataProvider, id)
  359. if _, ok := err.(*dataprovider.RecordNotFoundError); ok {
  360. renderNotFoundPage(w, err)
  361. return
  362. } else if err != nil {
  363. renderInternalServerErrorPage(w, err)
  364. return
  365. }
  366. updatedUser, err := getUserFromPostFields(r)
  367. if err != nil {
  368. renderUpdateUserPage(w, user, err.Error())
  369. return
  370. }
  371. updatedUser.ID = user.ID
  372. if len(updatedUser.Password) == 0 {
  373. updatedUser.Password = user.Password
  374. }
  375. err = dataprovider.UpdateUser(dataProvider, updatedUser)
  376. if err == nil {
  377. http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
  378. } else {
  379. renderUpdateUserPage(w, user, err.Error())
  380. }
  381. }
  382. func handleWebGetConnections(w http.ResponseWriter, r *http.Request) {
  383. connectionStats := sftpd.GetConnectionsStats()
  384. data := connectionsPage{
  385. basePage: getBasePageData(pageConnectionsTitle, webConnectionsPath),
  386. Connections: connectionStats,
  387. }
  388. renderTemplate(w, templateConnections, data)
  389. }