web.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  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 getUserFromPostFields(r *http.Request) (dataprovider.User, error) {
  205. var user dataprovider.User
  206. err := r.ParseForm()
  207. if err != nil {
  208. return user, err
  209. }
  210. publicKeysFormValue := r.Form.Get("public_keys")
  211. publicKeys := getSliceFromDelimitedValues(publicKeysFormValue, "\n")
  212. uid, err := strconv.Atoi(r.Form.Get("uid"))
  213. if err != nil {
  214. return user, err
  215. }
  216. gid, err := strconv.Atoi(r.Form.Get("gid"))
  217. if err != nil {
  218. return user, err
  219. }
  220. maxSessions, err := strconv.Atoi(r.Form.Get("max_sessions"))
  221. if err != nil {
  222. return user, err
  223. }
  224. quotaSize, err := strconv.ParseInt(r.Form.Get("quota_size"), 10, 64)
  225. if err != nil {
  226. return user, err
  227. }
  228. quotaFiles, err := strconv.Atoi(r.Form.Get("quota_files"))
  229. if err != nil {
  230. return user, err
  231. }
  232. bandwidthUL, err := strconv.ParseInt(r.Form.Get("upload_bandwidth"), 10, 64)
  233. if err != nil {
  234. return user, err
  235. }
  236. bandwidthDL, err := strconv.ParseInt(r.Form.Get("download_bandwidth"), 10, 64)
  237. if err != nil {
  238. return user, err
  239. }
  240. status, err := strconv.Atoi(r.Form.Get("status"))
  241. if err != nil {
  242. return user, err
  243. }
  244. expirationDateMillis := int64(0)
  245. expirationDateString := r.Form.Get("expiration_date")
  246. if len(strings.TrimSpace(expirationDateString)) > 0 {
  247. expirationDate, err := time.Parse(webDateTimeFormat, expirationDateString)
  248. if err != nil {
  249. return user, err
  250. }
  251. expirationDateMillis = utils.GetTimeAsMsSinceEpoch(expirationDate)
  252. }
  253. user = dataprovider.User{
  254. Username: r.Form.Get("username"),
  255. Password: r.Form.Get("password"),
  256. PublicKeys: publicKeys,
  257. HomeDir: r.Form.Get("home_dir"),
  258. UID: uid,
  259. GID: gid,
  260. Permissions: getUserPermissionsFromPostFields(r),
  261. MaxSessions: maxSessions,
  262. QuotaSize: quotaSize,
  263. QuotaFiles: quotaFiles,
  264. UploadBandwidth: bandwidthUL,
  265. DownloadBandwidth: bandwidthDL,
  266. Status: status,
  267. ExpirationDate: expirationDateMillis,
  268. Filters: getFiltersFromUserPostFields(r),
  269. }
  270. return user, err
  271. }
  272. func handleGetWebUsers(w http.ResponseWriter, r *http.Request) {
  273. limit := defaultUsersQueryLimit
  274. if _, ok := r.URL.Query()["qlimit"]; ok {
  275. var err error
  276. limit, err = strconv.Atoi(r.URL.Query().Get("qlimit"))
  277. if err != nil {
  278. limit = defaultUsersQueryLimit
  279. }
  280. }
  281. var users []dataprovider.User
  282. u, err := dataprovider.GetUsers(dataProvider, limit, 0, "ASC", "")
  283. users = append(users, u...)
  284. for len(u) == limit {
  285. u, err = dataprovider.GetUsers(dataProvider, limit, len(users), "ASC", "")
  286. if err == nil && len(u) > 0 {
  287. users = append(users, u...)
  288. } else {
  289. break
  290. }
  291. }
  292. if err != nil {
  293. renderInternalServerErrorPage(w, err)
  294. return
  295. }
  296. data := usersPage{
  297. basePage: getBasePageData(pageUsersTitle, webUsersPath),
  298. Users: users,
  299. }
  300. renderTemplate(w, templateUsers, data)
  301. }
  302. func handleWebAddUserGet(w http.ResponseWriter, r *http.Request) {
  303. renderAddUserPage(w, dataprovider.User{Status: 1}, "")
  304. }
  305. func handleWebUpdateUserGet(userID string, w http.ResponseWriter, r *http.Request) {
  306. id, err := strconv.ParseInt(userID, 10, 64)
  307. if err != nil {
  308. renderBadRequestPage(w, err)
  309. return
  310. }
  311. user, err := dataprovider.GetUserByID(dataProvider, id)
  312. if err == nil {
  313. renderUpdateUserPage(w, user, "")
  314. } else if _, ok := err.(*dataprovider.RecordNotFoundError); ok {
  315. renderNotFoundPage(w, err)
  316. } else {
  317. renderInternalServerErrorPage(w, err)
  318. }
  319. }
  320. func handleWebAddUserPost(w http.ResponseWriter, r *http.Request) {
  321. user, err := getUserFromPostFields(r)
  322. if err != nil {
  323. renderAddUserPage(w, user, err.Error())
  324. return
  325. }
  326. err = dataprovider.AddUser(dataProvider, user)
  327. if err == nil {
  328. http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
  329. } else {
  330. renderAddUserPage(w, user, err.Error())
  331. }
  332. }
  333. func handleWebUpdateUserPost(userID string, w http.ResponseWriter, r *http.Request) {
  334. id, err := strconv.ParseInt(userID, 10, 64)
  335. if err != nil {
  336. renderBadRequestPage(w, err)
  337. return
  338. }
  339. user, err := dataprovider.GetUserByID(dataProvider, id)
  340. if _, ok := err.(*dataprovider.RecordNotFoundError); ok {
  341. renderNotFoundPage(w, err)
  342. return
  343. } else if err != nil {
  344. renderInternalServerErrorPage(w, err)
  345. return
  346. }
  347. updatedUser, err := getUserFromPostFields(r)
  348. if err != nil {
  349. renderUpdateUserPage(w, user, err.Error())
  350. return
  351. }
  352. updatedUser.ID = user.ID
  353. if len(updatedUser.Password) == 0 {
  354. updatedUser.Password = user.Password
  355. }
  356. err = dataprovider.UpdateUser(dataProvider, updatedUser)
  357. if err == nil {
  358. http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
  359. } else {
  360. renderUpdateUserPage(w, user, err.Error())
  361. }
  362. }
  363. func handleWebGetConnections(w http.ResponseWriter, r *http.Request) {
  364. connectionStats := sftpd.GetConnectionsStats()
  365. data := connectionsPage{
  366. basePage: getBasePageData(pageConnectionsTitle, webConnectionsPath),
  367. Connections: connectionStats,
  368. }
  369. renderTemplate(w, templateConnections, data)
  370. }