web.go 13 KB

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