webadmin.go 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680
  1. package httpd
  2. import (
  3. "errors"
  4. "fmt"
  5. "html/template"
  6. "io"
  7. "net/http"
  8. "net/url"
  9. "path/filepath"
  10. "strconv"
  11. "strings"
  12. "time"
  13. "github.com/go-chi/render"
  14. "github.com/drakkan/sftpgo/v2/common"
  15. "github.com/drakkan/sftpgo/v2/dataprovider"
  16. "github.com/drakkan/sftpgo/v2/kms"
  17. "github.com/drakkan/sftpgo/v2/sdk"
  18. "github.com/drakkan/sftpgo/v2/util"
  19. "github.com/drakkan/sftpgo/v2/version"
  20. "github.com/drakkan/sftpgo/v2/vfs"
  21. )
  22. type userPageMode int
  23. const (
  24. userPageModeAdd userPageMode = iota + 1
  25. userPageModeUpdate
  26. userPageModeTemplate
  27. )
  28. type folderPageMode int
  29. const (
  30. folderPageModeAdd folderPageMode = iota + 1
  31. folderPageModeUpdate
  32. folderPageModeTemplate
  33. )
  34. const (
  35. templateAdminDir = "webadmin"
  36. templateBase = "base.html"
  37. templateFsConfig = "fsconfig.html"
  38. templateUsers = "users.html"
  39. templateUser = "user.html"
  40. templateAdmins = "admins.html"
  41. templateAdmin = "admin.html"
  42. templateConnections = "connections.html"
  43. templateFolders = "folders.html"
  44. templateFolder = "folder.html"
  45. templateMessage = "message.html"
  46. templateStatus = "status.html"
  47. templateLogin = "login.html"
  48. templateDefender = "defender.html"
  49. templateCredentials = "credentials.html"
  50. templateMaintenance = "maintenance.html"
  51. templateSetup = "adminsetup.html"
  52. pageUsersTitle = "Users"
  53. pageAdminsTitle = "Admins"
  54. pageConnectionsTitle = "Connections"
  55. pageStatusTitle = "Status"
  56. pageFoldersTitle = "Folders"
  57. pageCredentialsTitle = "Manage credentials"
  58. pageMaintenanceTitle = "Maintenance"
  59. pageDefenderTitle = "Defender"
  60. pageSetupTitle = "Create first admin user"
  61. defaultQueryLimit = 500
  62. )
  63. var (
  64. adminTemplates = make(map[string]*template.Template)
  65. )
  66. type basePage struct {
  67. Title string
  68. CurrentURL string
  69. UsersURL string
  70. UserURL string
  71. UserTemplateURL string
  72. AdminsURL string
  73. AdminURL string
  74. QuotaScanURL string
  75. ConnectionsURL string
  76. FoldersURL string
  77. FolderURL string
  78. FolderTemplateURL string
  79. DefenderURL string
  80. LogoutURL string
  81. CredentialsURL string
  82. FolderQuotaScanURL string
  83. StatusURL string
  84. MaintenanceURL string
  85. StaticURL string
  86. UsersTitle string
  87. AdminsTitle string
  88. ConnectionsTitle string
  89. FoldersTitle string
  90. StatusTitle string
  91. MaintenanceTitle string
  92. DefenderTitle string
  93. Version string
  94. CSRFToken string
  95. HasDefender bool
  96. LoggedAdmin *dataprovider.Admin
  97. }
  98. type usersPage struct {
  99. basePage
  100. Users []dataprovider.User
  101. }
  102. type adminsPage struct {
  103. basePage
  104. Admins []dataprovider.Admin
  105. }
  106. type foldersPage struct {
  107. basePage
  108. Folders []vfs.BaseVirtualFolder
  109. }
  110. type connectionsPage struct {
  111. basePage
  112. Connections []*common.ConnectionStatus
  113. }
  114. type statusPage struct {
  115. basePage
  116. Status ServicesStatus
  117. }
  118. type userPage struct {
  119. basePage
  120. User *dataprovider.User
  121. RootPerms []string
  122. Error string
  123. ValidPerms []string
  124. ValidLoginMethods []string
  125. ValidProtocols []string
  126. WebClientOptions []string
  127. RootDirPerms []string
  128. RedactedSecret string
  129. Mode userPageMode
  130. VirtualFolders []vfs.BaseVirtualFolder
  131. }
  132. type adminPage struct {
  133. basePage
  134. Admin *dataprovider.Admin
  135. Error string
  136. IsAdd bool
  137. }
  138. type credentialsPage struct {
  139. basePage
  140. Error string
  141. AllowAPIKeyAuth bool
  142. ChangePwdURL string
  143. ManageAPIKeyURL string
  144. APIKeyError string
  145. }
  146. type maintenancePage struct {
  147. basePage
  148. BackupPath string
  149. RestorePath string
  150. Error string
  151. }
  152. type defenderHostsPage struct {
  153. basePage
  154. DefenderHostsURL string
  155. }
  156. type setupPage struct {
  157. basePage
  158. Username string
  159. Error string
  160. }
  161. type folderPage struct {
  162. basePage
  163. Folder vfs.BaseVirtualFolder
  164. Error string
  165. Mode folderPageMode
  166. }
  167. type messagePage struct {
  168. basePage
  169. Error string
  170. Success string
  171. }
  172. type userTemplateFields struct {
  173. Username string
  174. Password string
  175. PublicKey string
  176. }
  177. func loadAdminTemplates(templatesPath string) {
  178. usersPaths := []string{
  179. filepath.Join(templatesPath, templateAdminDir, templateBase),
  180. filepath.Join(templatesPath, templateAdminDir, templateUsers),
  181. }
  182. userPaths := []string{
  183. filepath.Join(templatesPath, templateAdminDir, templateBase),
  184. filepath.Join(templatesPath, templateAdminDir, templateFsConfig),
  185. filepath.Join(templatesPath, templateAdminDir, templateUser),
  186. }
  187. adminsPaths := []string{
  188. filepath.Join(templatesPath, templateAdminDir, templateBase),
  189. filepath.Join(templatesPath, templateAdminDir, templateAdmins),
  190. }
  191. adminPaths := []string{
  192. filepath.Join(templatesPath, templateAdminDir, templateBase),
  193. filepath.Join(templatesPath, templateAdminDir, templateAdmin),
  194. }
  195. credentialsPaths := []string{
  196. filepath.Join(templatesPath, templateAdminDir, templateBase),
  197. filepath.Join(templatesPath, templateAdminDir, templateCredentials),
  198. }
  199. connectionsPaths := []string{
  200. filepath.Join(templatesPath, templateAdminDir, templateBase),
  201. filepath.Join(templatesPath, templateAdminDir, templateConnections),
  202. }
  203. messagePath := []string{
  204. filepath.Join(templatesPath, templateAdminDir, templateBase),
  205. filepath.Join(templatesPath, templateAdminDir, templateMessage),
  206. }
  207. foldersPath := []string{
  208. filepath.Join(templatesPath, templateAdminDir, templateBase),
  209. filepath.Join(templatesPath, templateAdminDir, templateFolders),
  210. }
  211. folderPath := []string{
  212. filepath.Join(templatesPath, templateAdminDir, templateBase),
  213. filepath.Join(templatesPath, templateAdminDir, templateFsConfig),
  214. filepath.Join(templatesPath, templateAdminDir, templateFolder),
  215. }
  216. statusPath := []string{
  217. filepath.Join(templatesPath, templateAdminDir, templateBase),
  218. filepath.Join(templatesPath, templateAdminDir, templateStatus),
  219. }
  220. loginPath := []string{
  221. filepath.Join(templatesPath, templateAdminDir, templateLogin),
  222. }
  223. maintenancePath := []string{
  224. filepath.Join(templatesPath, templateAdminDir, templateBase),
  225. filepath.Join(templatesPath, templateAdminDir, templateMaintenance),
  226. }
  227. defenderPath := []string{
  228. filepath.Join(templatesPath, templateAdminDir, templateBase),
  229. filepath.Join(templatesPath, templateAdminDir, templateDefender),
  230. }
  231. setupPath := []string{
  232. filepath.Join(templatesPath, templateAdminDir, templateSetup),
  233. }
  234. rootTpl := template.New("").Funcs(template.FuncMap{
  235. "ListFSProviders": sdk.ListProviders,
  236. })
  237. usersTmpl := util.LoadTemplate(rootTpl, usersPaths...)
  238. userTmpl := util.LoadTemplate(rootTpl, userPaths...)
  239. adminsTmpl := util.LoadTemplate(rootTpl, adminsPaths...)
  240. adminTmpl := util.LoadTemplate(rootTpl, adminPaths...)
  241. connectionsTmpl := util.LoadTemplate(rootTpl, connectionsPaths...)
  242. messageTmpl := util.LoadTemplate(rootTpl, messagePath...)
  243. foldersTmpl := util.LoadTemplate(rootTpl, foldersPath...)
  244. folderTmpl := util.LoadTemplate(rootTpl, folderPath...)
  245. statusTmpl := util.LoadTemplate(rootTpl, statusPath...)
  246. loginTmpl := util.LoadTemplate(rootTpl, loginPath...)
  247. credentialsTmpl := util.LoadTemplate(rootTpl, credentialsPaths...)
  248. maintenanceTmpl := util.LoadTemplate(rootTpl, maintenancePath...)
  249. defenderTmpl := util.LoadTemplate(rootTpl, defenderPath...)
  250. setupTmpl := util.LoadTemplate(rootTpl, setupPath...)
  251. adminTemplates[templateUsers] = usersTmpl
  252. adminTemplates[templateUser] = userTmpl
  253. adminTemplates[templateAdmins] = adminsTmpl
  254. adminTemplates[templateAdmin] = adminTmpl
  255. adminTemplates[templateConnections] = connectionsTmpl
  256. adminTemplates[templateMessage] = messageTmpl
  257. adminTemplates[templateFolders] = foldersTmpl
  258. adminTemplates[templateFolder] = folderTmpl
  259. adminTemplates[templateStatus] = statusTmpl
  260. adminTemplates[templateLogin] = loginTmpl
  261. adminTemplates[templateCredentials] = credentialsTmpl
  262. adminTemplates[templateMaintenance] = maintenanceTmpl
  263. adminTemplates[templateDefender] = defenderTmpl
  264. adminTemplates[templateSetup] = setupTmpl
  265. }
  266. func getBasePageData(title, currentURL string, r *http.Request) basePage {
  267. var csrfToken string
  268. if currentURL != "" {
  269. csrfToken = createCSRFToken()
  270. }
  271. return basePage{
  272. Title: title,
  273. CurrentURL: currentURL,
  274. UsersURL: webUsersPath,
  275. UserURL: webUserPath,
  276. UserTemplateURL: webTemplateUser,
  277. AdminsURL: webAdminsPath,
  278. AdminURL: webAdminPath,
  279. FoldersURL: webFoldersPath,
  280. FolderURL: webFolderPath,
  281. FolderTemplateURL: webTemplateFolder,
  282. DefenderURL: webDefenderPath,
  283. LogoutURL: webLogoutPath,
  284. CredentialsURL: webAdminCredentialsPath,
  285. QuotaScanURL: webQuotaScanPath,
  286. ConnectionsURL: webConnectionsPath,
  287. StatusURL: webStatusPath,
  288. FolderQuotaScanURL: webScanVFolderPath,
  289. MaintenanceURL: webMaintenancePath,
  290. StaticURL: webStaticFilesPath,
  291. UsersTitle: pageUsersTitle,
  292. AdminsTitle: pageAdminsTitle,
  293. ConnectionsTitle: pageConnectionsTitle,
  294. FoldersTitle: pageFoldersTitle,
  295. StatusTitle: pageStatusTitle,
  296. MaintenanceTitle: pageMaintenanceTitle,
  297. DefenderTitle: pageDefenderTitle,
  298. Version: version.GetAsString(),
  299. LoggedAdmin: getAdminFromToken(r),
  300. HasDefender: common.Config.DefenderConfig.Enabled,
  301. CSRFToken: csrfToken,
  302. }
  303. }
  304. func renderAdminTemplate(w http.ResponseWriter, tmplName string, data interface{}) {
  305. err := adminTemplates[tmplName].ExecuteTemplate(w, tmplName, data)
  306. if err != nil {
  307. http.Error(w, err.Error(), http.StatusInternalServerError)
  308. }
  309. }
  310. func renderMessagePage(w http.ResponseWriter, r *http.Request, title, body string, statusCode int, err error, message string) {
  311. var errorString string
  312. if body != "" {
  313. errorString = body + " "
  314. }
  315. if err != nil {
  316. errorString += err.Error()
  317. }
  318. data := messagePage{
  319. basePage: getBasePageData(title, "", r),
  320. Error: errorString,
  321. Success: message,
  322. }
  323. w.WriteHeader(statusCode)
  324. renderAdminTemplate(w, templateMessage, data)
  325. }
  326. func renderInternalServerErrorPage(w http.ResponseWriter, r *http.Request, err error) {
  327. renderMessagePage(w, r, page500Title, page500Body, http.StatusInternalServerError, err, "")
  328. }
  329. func renderBadRequestPage(w http.ResponseWriter, r *http.Request, err error) {
  330. renderMessagePage(w, r, page400Title, "", http.StatusBadRequest, err, "")
  331. }
  332. func renderForbiddenPage(w http.ResponseWriter, r *http.Request, body string) {
  333. renderMessagePage(w, r, page403Title, "", http.StatusForbidden, nil, body)
  334. }
  335. func renderNotFoundPage(w http.ResponseWriter, r *http.Request, err error) {
  336. renderMessagePage(w, r, page404Title, page404Body, http.StatusNotFound, err, "")
  337. }
  338. func renderCredentialsPage(w http.ResponseWriter, r *http.Request, pwdError, apiKeyError string) {
  339. data := credentialsPage{
  340. basePage: getBasePageData(pageCredentialsTitle, webAdminCredentialsPath, r),
  341. ChangePwdURL: webChangeAdminPwdPath,
  342. ManageAPIKeyURL: webChangeAdminAPIKeyAccessPath,
  343. Error: pwdError,
  344. APIKeyError: apiKeyError,
  345. }
  346. admin, err := dataprovider.AdminExists(data.LoggedAdmin.Username)
  347. if err != nil {
  348. renderInternalServerErrorPage(w, r, err)
  349. return
  350. }
  351. data.AllowAPIKeyAuth = admin.Filters.AllowAPIKeyAuth
  352. renderAdminTemplate(w, templateCredentials, data)
  353. }
  354. func renderMaintenancePage(w http.ResponseWriter, r *http.Request, error string) {
  355. data := maintenancePage{
  356. basePage: getBasePageData(pageMaintenanceTitle, webMaintenancePath, r),
  357. BackupPath: webBackupPath,
  358. RestorePath: webRestorePath,
  359. Error: error,
  360. }
  361. renderAdminTemplate(w, templateMaintenance, data)
  362. }
  363. func renderAdminSetupPage(w http.ResponseWriter, r *http.Request, username, error string) {
  364. data := setupPage{
  365. basePage: getBasePageData(pageSetupTitle, webAdminSetupPath, r),
  366. Username: username,
  367. Error: error,
  368. }
  369. renderAdminTemplate(w, templateSetup, data)
  370. }
  371. func renderAddUpdateAdminPage(w http.ResponseWriter, r *http.Request, admin *dataprovider.Admin,
  372. error string, isAdd bool) {
  373. currentURL := webAdminPath
  374. if !isAdd {
  375. currentURL = fmt.Sprintf("%v/%v", webAdminPath, url.PathEscape(admin.Username))
  376. }
  377. data := adminPage{
  378. basePage: getBasePageData("Add a new user", currentURL, r),
  379. Admin: admin,
  380. Error: error,
  381. IsAdd: isAdd,
  382. }
  383. renderAdminTemplate(w, templateAdmin, data)
  384. }
  385. func renderUserPage(w http.ResponseWriter, r *http.Request, user *dataprovider.User, mode userPageMode, error string) {
  386. folders, err := getWebVirtualFolders(w, r, defaultQueryLimit)
  387. if err != nil {
  388. return
  389. }
  390. user.SetEmptySecretsIfNil()
  391. var title, currentURL string
  392. switch mode {
  393. case userPageModeAdd:
  394. title = "Add a new user"
  395. currentURL = webUserPath
  396. case userPageModeUpdate:
  397. title = "Update user"
  398. currentURL = fmt.Sprintf("%v/%v", webUserPath, url.PathEscape(user.Username))
  399. case userPageModeTemplate:
  400. title = "User template"
  401. currentURL = webTemplateUser
  402. }
  403. if user.Password != "" && user.IsPasswordHashed() && mode == userPageModeUpdate {
  404. user.Password = redactedSecret
  405. }
  406. user.FsConfig.RedactedSecret = redactedSecret
  407. data := userPage{
  408. basePage: getBasePageData(title, currentURL, r),
  409. Mode: mode,
  410. Error: error,
  411. User: user,
  412. ValidPerms: dataprovider.ValidPerms,
  413. ValidLoginMethods: dataprovider.ValidLoginMethods,
  414. ValidProtocols: dataprovider.ValidProtocols,
  415. WebClientOptions: sdk.WebClientOptions,
  416. RootDirPerms: user.GetPermissionsForPath("/"),
  417. VirtualFolders: folders,
  418. }
  419. renderAdminTemplate(w, templateUser, data)
  420. }
  421. func renderFolderPage(w http.ResponseWriter, r *http.Request, folder vfs.BaseVirtualFolder, mode folderPageMode, error string) {
  422. var title, currentURL string
  423. switch mode {
  424. case folderPageModeAdd:
  425. title = "Add a new folder"
  426. currentURL = webFolderPath
  427. case folderPageModeUpdate:
  428. title = "Update folder"
  429. currentURL = fmt.Sprintf("%v/%v", webFolderPath, url.PathEscape(folder.Name))
  430. case folderPageModeTemplate:
  431. title = "Folder template"
  432. currentURL = webTemplateFolder
  433. }
  434. folder.FsConfig.RedactedSecret = redactedSecret
  435. folder.FsConfig.SetEmptySecretsIfNil()
  436. data := folderPage{
  437. basePage: getBasePageData(title, currentURL, r),
  438. Error: error,
  439. Folder: folder,
  440. Mode: mode,
  441. }
  442. renderAdminTemplate(w, templateFolder, data)
  443. }
  444. func getFoldersForTemplate(r *http.Request) []string {
  445. var res []string
  446. folderNames := r.Form["tpl_foldername"]
  447. folders := make(map[string]bool)
  448. for _, name := range folderNames {
  449. name = strings.TrimSpace(name)
  450. if name == "" {
  451. continue
  452. }
  453. if _, ok := folders[name]; ok {
  454. continue
  455. }
  456. folders[name] = true
  457. res = append(res, name)
  458. }
  459. return res
  460. }
  461. func getUsersForTemplate(r *http.Request) []userTemplateFields {
  462. var res []userTemplateFields
  463. tplUsernames := r.Form["tpl_username"]
  464. tplPasswords := r.Form["tpl_password"]
  465. tplPublicKeys := r.Form["tpl_public_keys"]
  466. users := make(map[string]bool)
  467. for idx, username := range tplUsernames {
  468. username = strings.TrimSpace(username)
  469. password := ""
  470. publicKey := ""
  471. if len(tplPasswords) > idx {
  472. password = strings.TrimSpace(tplPasswords[idx])
  473. }
  474. if len(tplPublicKeys) > idx {
  475. publicKey = strings.TrimSpace(tplPublicKeys[idx])
  476. }
  477. if username == "" || (password == "" && publicKey == "") {
  478. continue
  479. }
  480. if _, ok := users[username]; ok {
  481. continue
  482. }
  483. users[username] = true
  484. res = append(res, userTemplateFields{
  485. Username: username,
  486. Password: password,
  487. PublicKey: publicKey,
  488. })
  489. }
  490. return res
  491. }
  492. func getVirtualFoldersFromPostFields(r *http.Request) []vfs.VirtualFolder {
  493. var virtualFolders []vfs.VirtualFolder
  494. folderPaths := r.Form["vfolder_path"]
  495. folderNames := r.Form["vfolder_name"]
  496. folderQuotaSizes := r.Form["vfolder_quota_size"]
  497. folderQuotaFiles := r.Form["vfolder_quota_files"]
  498. for idx, p := range folderPaths {
  499. p = strings.TrimSpace(p)
  500. name := ""
  501. if len(folderNames) > idx {
  502. name = folderNames[idx]
  503. }
  504. if p != "" && name != "" {
  505. vfolder := vfs.VirtualFolder{
  506. BaseVirtualFolder: vfs.BaseVirtualFolder{
  507. Name: name,
  508. },
  509. VirtualPath: p,
  510. QuotaFiles: -1,
  511. QuotaSize: -1,
  512. }
  513. if len(folderQuotaSizes) > idx {
  514. quotaSize, err := strconv.ParseInt(strings.TrimSpace(folderQuotaSizes[idx]), 10, 64)
  515. if err == nil {
  516. vfolder.QuotaSize = quotaSize
  517. }
  518. }
  519. if len(folderQuotaFiles) > idx {
  520. quotaFiles, err := strconv.Atoi(strings.TrimSpace(folderQuotaFiles[idx]))
  521. if err == nil {
  522. vfolder.QuotaFiles = quotaFiles
  523. }
  524. }
  525. virtualFolders = append(virtualFolders, vfolder)
  526. }
  527. }
  528. return virtualFolders
  529. }
  530. func getUserPermissionsFromPostFields(r *http.Request) map[string][]string {
  531. permissions := make(map[string][]string)
  532. permissions["/"] = r.Form["permissions"]
  533. for k := range r.Form {
  534. if strings.HasPrefix(k, "sub_perm_path") {
  535. p := strings.TrimSpace(r.Form.Get(k))
  536. if p != "" {
  537. idx := strings.TrimPrefix(k, "sub_perm_path")
  538. permissions[p] = r.Form[fmt.Sprintf("sub_perm_permissions%v", idx)]
  539. }
  540. }
  541. }
  542. return permissions
  543. }
  544. func getFilePatternsFromPostField(r *http.Request) []sdk.PatternsFilter {
  545. var result []sdk.PatternsFilter
  546. allowedPatterns := make(map[string][]string)
  547. deniedPatterns := make(map[string][]string)
  548. for k := range r.Form {
  549. if strings.HasPrefix(k, "pattern_path") {
  550. p := strings.TrimSpace(r.Form.Get(k))
  551. idx := strings.TrimPrefix(k, "pattern_path")
  552. filters := strings.TrimSpace(r.Form.Get(fmt.Sprintf("patterns%v", idx)))
  553. filters = strings.ReplaceAll(filters, " ", "")
  554. patternType := r.Form.Get(fmt.Sprintf("pattern_type%v", idx))
  555. if p != "" && filters != "" {
  556. if patternType == "allowed" {
  557. allowedPatterns[p] = append(allowedPatterns[p], strings.Split(filters, ",")...)
  558. } else {
  559. deniedPatterns[p] = append(deniedPatterns[p], strings.Split(filters, ",")...)
  560. }
  561. }
  562. }
  563. }
  564. for dirAllowed, allowPatterns := range allowedPatterns {
  565. filter := sdk.PatternsFilter{
  566. Path: dirAllowed,
  567. AllowedPatterns: util.RemoveDuplicates(allowPatterns),
  568. }
  569. for dirDenied, denPatterns := range deniedPatterns {
  570. if dirAllowed == dirDenied {
  571. filter.DeniedPatterns = util.RemoveDuplicates(denPatterns)
  572. break
  573. }
  574. }
  575. result = append(result, filter)
  576. }
  577. for dirDenied, denPatterns := range deniedPatterns {
  578. found := false
  579. for _, res := range result {
  580. if res.Path == dirDenied {
  581. found = true
  582. break
  583. }
  584. }
  585. if !found {
  586. result = append(result, sdk.PatternsFilter{
  587. Path: dirDenied,
  588. DeniedPatterns: denPatterns,
  589. })
  590. }
  591. }
  592. return result
  593. }
  594. func getFiltersFromUserPostFields(r *http.Request) sdk.UserFilters {
  595. var filters sdk.UserFilters
  596. filters.AllowedIP = getSliceFromDelimitedValues(r.Form.Get("allowed_ip"), ",")
  597. filters.DeniedIP = getSliceFromDelimitedValues(r.Form.Get("denied_ip"), ",")
  598. filters.DeniedLoginMethods = r.Form["ssh_login_methods"]
  599. filters.DeniedProtocols = r.Form["denied_protocols"]
  600. filters.FilePatterns = getFilePatternsFromPostField(r)
  601. filters.TLSUsername = sdk.TLSUsername(r.Form.Get("tls_username"))
  602. filters.WebClient = r.Form["web_client_options"]
  603. hooks := r.Form["hooks"]
  604. if util.IsStringInSlice("external_auth_disabled", hooks) {
  605. filters.Hooks.ExternalAuthDisabled = true
  606. }
  607. if util.IsStringInSlice("pre_login_disabled", hooks) {
  608. filters.Hooks.PreLoginDisabled = true
  609. }
  610. if util.IsStringInSlice("check_password_disabled", hooks) {
  611. filters.Hooks.CheckPasswordDisabled = true
  612. }
  613. filters.DisableFsChecks = len(r.Form.Get("disable_fs_checks")) > 0
  614. filters.AllowAPIKeyAuth = len(r.Form.Get("allow_api_key_auth")) > 0
  615. return filters
  616. }
  617. func getSecretFromFormField(r *http.Request, field string) *kms.Secret {
  618. secret := kms.NewPlainSecret(r.Form.Get(field))
  619. if strings.TrimSpace(secret.GetPayload()) == redactedSecret {
  620. secret.SetStatus(kms.SecretStatusRedacted)
  621. }
  622. if strings.TrimSpace(secret.GetPayload()) == "" {
  623. secret.SetStatus("")
  624. }
  625. return secret
  626. }
  627. func getS3Config(r *http.Request) (vfs.S3FsConfig, error) {
  628. var err error
  629. config := vfs.S3FsConfig{}
  630. config.Bucket = r.Form.Get("s3_bucket")
  631. config.Region = r.Form.Get("s3_region")
  632. config.AccessKey = r.Form.Get("s3_access_key")
  633. config.AccessSecret = getSecretFromFormField(r, "s3_access_secret")
  634. config.Endpoint = r.Form.Get("s3_endpoint")
  635. config.StorageClass = r.Form.Get("s3_storage_class")
  636. config.KeyPrefix = r.Form.Get("s3_key_prefix")
  637. config.UploadPartSize, err = strconv.ParseInt(r.Form.Get("s3_upload_part_size"), 10, 64)
  638. if err != nil {
  639. return config, err
  640. }
  641. config.UploadConcurrency, err = strconv.Atoi(r.Form.Get("s3_upload_concurrency"))
  642. if err != nil {
  643. return config, err
  644. }
  645. config.DownloadPartSize, err = strconv.ParseInt(r.Form.Get("s3_download_part_size"), 10, 64)
  646. if err != nil {
  647. return config, err
  648. }
  649. config.DownloadConcurrency, err = strconv.Atoi(r.Form.Get("s3_download_concurrency"))
  650. if err != nil {
  651. return config, err
  652. }
  653. config.ForcePathStyle = r.Form.Get("s3_force_path_style") != ""
  654. config.DownloadPartMaxTime, err = strconv.Atoi(r.Form.Get("s3_download_part_max_time"))
  655. return config, err
  656. }
  657. func getGCSConfig(r *http.Request) (vfs.GCSFsConfig, error) {
  658. var err error
  659. config := vfs.GCSFsConfig{}
  660. config.Bucket = r.Form.Get("gcs_bucket")
  661. config.StorageClass = r.Form.Get("gcs_storage_class")
  662. config.KeyPrefix = r.Form.Get("gcs_key_prefix")
  663. autoCredentials := r.Form.Get("gcs_auto_credentials")
  664. if autoCredentials != "" {
  665. config.AutomaticCredentials = 1
  666. } else {
  667. config.AutomaticCredentials = 0
  668. }
  669. credentials, _, err := r.FormFile("gcs_credential_file")
  670. if err == http.ErrMissingFile {
  671. return config, nil
  672. }
  673. if err != nil {
  674. return config, err
  675. }
  676. defer credentials.Close()
  677. fileBytes, err := io.ReadAll(credentials)
  678. if err != nil || len(fileBytes) == 0 {
  679. if len(fileBytes) == 0 {
  680. err = errors.New("credentials file size must be greater than 0")
  681. }
  682. return config, err
  683. }
  684. config.Credentials = kms.NewPlainSecret(string(fileBytes))
  685. config.AutomaticCredentials = 0
  686. return config, err
  687. }
  688. func getSFTPConfig(r *http.Request) (vfs.SFTPFsConfig, error) {
  689. var err error
  690. config := vfs.SFTPFsConfig{}
  691. config.Endpoint = r.Form.Get("sftp_endpoint")
  692. config.Username = r.Form.Get("sftp_username")
  693. config.Password = getSecretFromFormField(r, "sftp_password")
  694. config.PrivateKey = getSecretFromFormField(r, "sftp_private_key")
  695. fingerprintsFormValue := r.Form.Get("sftp_fingerprints")
  696. config.Fingerprints = getSliceFromDelimitedValues(fingerprintsFormValue, "\n")
  697. config.Prefix = r.Form.Get("sftp_prefix")
  698. config.DisableCouncurrentReads = len(r.Form.Get("sftp_disable_concurrent_reads")) > 0
  699. config.BufferSize, err = strconv.ParseInt(r.Form.Get("sftp_buffer_size"), 10, 64)
  700. return config, err
  701. }
  702. func getAzureConfig(r *http.Request) (vfs.AzBlobFsConfig, error) {
  703. var err error
  704. config := vfs.AzBlobFsConfig{}
  705. config.Container = r.Form.Get("az_container")
  706. config.AccountName = r.Form.Get("az_account_name")
  707. config.AccountKey = getSecretFromFormField(r, "az_account_key")
  708. config.SASURL = getSecretFromFormField(r, "az_sas_url")
  709. config.Endpoint = r.Form.Get("az_endpoint")
  710. config.KeyPrefix = r.Form.Get("az_key_prefix")
  711. config.AccessTier = r.Form.Get("az_access_tier")
  712. config.UseEmulator = len(r.Form.Get("az_use_emulator")) > 0
  713. config.UploadPartSize, err = strconv.ParseInt(r.Form.Get("az_upload_part_size"), 10, 64)
  714. if err != nil {
  715. return config, err
  716. }
  717. config.UploadConcurrency, err = strconv.Atoi(r.Form.Get("az_upload_concurrency"))
  718. return config, err
  719. }
  720. func getFsConfigFromPostFields(r *http.Request) (vfs.Filesystem, error) {
  721. var fs vfs.Filesystem
  722. fs.Provider = sdk.GetProviderByName(r.Form.Get("fs_provider"))
  723. switch fs.Provider {
  724. case sdk.S3FilesystemProvider:
  725. config, err := getS3Config(r)
  726. if err != nil {
  727. return fs, err
  728. }
  729. fs.S3Config = config
  730. case sdk.AzureBlobFilesystemProvider:
  731. config, err := getAzureConfig(r)
  732. if err != nil {
  733. return fs, err
  734. }
  735. fs.AzBlobConfig = config
  736. case sdk.GCSFilesystemProvider:
  737. config, err := getGCSConfig(r)
  738. if err != nil {
  739. return fs, err
  740. }
  741. fs.GCSConfig = config
  742. case sdk.CryptedFilesystemProvider:
  743. fs.CryptConfig.Passphrase = getSecretFromFormField(r, "crypt_passphrase")
  744. case sdk.SFTPFilesystemProvider:
  745. config, err := getSFTPConfig(r)
  746. if err != nil {
  747. return fs, err
  748. }
  749. fs.SFTPConfig = config
  750. }
  751. return fs, nil
  752. }
  753. func getAdminFromPostFields(r *http.Request) (dataprovider.Admin, error) {
  754. var admin dataprovider.Admin
  755. err := r.ParseForm()
  756. if err != nil {
  757. return admin, err
  758. }
  759. status, err := strconv.Atoi(r.Form.Get("status"))
  760. if err != nil {
  761. return admin, err
  762. }
  763. admin.Username = r.Form.Get("username")
  764. admin.Password = r.Form.Get("password")
  765. admin.Permissions = r.Form["permissions"]
  766. admin.Email = r.Form.Get("email")
  767. admin.Status = status
  768. admin.Filters.AllowList = getSliceFromDelimitedValues(r.Form.Get("allowed_ip"), ",")
  769. admin.Filters.AllowAPIKeyAuth = len(r.Form.Get("allow_api_key_auth")) > 0
  770. admin.AdditionalInfo = r.Form.Get("additional_info")
  771. admin.Description = r.Form.Get("description")
  772. return admin, nil
  773. }
  774. func replacePlaceholders(field string, replacements map[string]string) string {
  775. for k, v := range replacements {
  776. field = strings.ReplaceAll(field, k, v)
  777. }
  778. return field
  779. }
  780. func getFolderFromTemplate(folder vfs.BaseVirtualFolder, name string) vfs.BaseVirtualFolder {
  781. folder.Name = name
  782. replacements := make(map[string]string)
  783. replacements["%name%"] = folder.Name
  784. folder.MappedPath = replacePlaceholders(folder.MappedPath, replacements)
  785. folder.Description = replacePlaceholders(folder.Description, replacements)
  786. switch folder.FsConfig.Provider {
  787. case sdk.CryptedFilesystemProvider:
  788. folder.FsConfig.CryptConfig = getCryptFsFromTemplate(folder.FsConfig.CryptConfig, replacements)
  789. case sdk.S3FilesystemProvider:
  790. folder.FsConfig.S3Config = getS3FsFromTemplate(folder.FsConfig.S3Config, replacements)
  791. case sdk.GCSFilesystemProvider:
  792. folder.FsConfig.GCSConfig = getGCSFsFromTemplate(folder.FsConfig.GCSConfig, replacements)
  793. case sdk.AzureBlobFilesystemProvider:
  794. folder.FsConfig.AzBlobConfig = getAzBlobFsFromTemplate(folder.FsConfig.AzBlobConfig, replacements)
  795. case sdk.SFTPFilesystemProvider:
  796. folder.FsConfig.SFTPConfig = getSFTPFsFromTemplate(folder.FsConfig.SFTPConfig, replacements)
  797. }
  798. return folder
  799. }
  800. func getCryptFsFromTemplate(fsConfig vfs.CryptFsConfig, replacements map[string]string) vfs.CryptFsConfig {
  801. if fsConfig.Passphrase != nil {
  802. if fsConfig.Passphrase.IsPlain() {
  803. payload := replacePlaceholders(fsConfig.Passphrase.GetPayload(), replacements)
  804. fsConfig.Passphrase = kms.NewPlainSecret(payload)
  805. }
  806. }
  807. return fsConfig
  808. }
  809. func getS3FsFromTemplate(fsConfig vfs.S3FsConfig, replacements map[string]string) vfs.S3FsConfig {
  810. fsConfig.KeyPrefix = replacePlaceholders(fsConfig.KeyPrefix, replacements)
  811. fsConfig.AccessKey = replacePlaceholders(fsConfig.AccessKey, replacements)
  812. if fsConfig.AccessSecret != nil && fsConfig.AccessSecret.IsPlain() {
  813. payload := replacePlaceholders(fsConfig.AccessSecret.GetPayload(), replacements)
  814. fsConfig.AccessSecret = kms.NewPlainSecret(payload)
  815. }
  816. return fsConfig
  817. }
  818. func getGCSFsFromTemplate(fsConfig vfs.GCSFsConfig, replacements map[string]string) vfs.GCSFsConfig {
  819. fsConfig.KeyPrefix = replacePlaceholders(fsConfig.KeyPrefix, replacements)
  820. return fsConfig
  821. }
  822. func getAzBlobFsFromTemplate(fsConfig vfs.AzBlobFsConfig, replacements map[string]string) vfs.AzBlobFsConfig {
  823. fsConfig.KeyPrefix = replacePlaceholders(fsConfig.KeyPrefix, replacements)
  824. fsConfig.AccountName = replacePlaceholders(fsConfig.AccountName, replacements)
  825. if fsConfig.AccountKey != nil && fsConfig.AccountKey.IsPlain() {
  826. payload := replacePlaceholders(fsConfig.AccountKey.GetPayload(), replacements)
  827. fsConfig.AccountKey = kms.NewPlainSecret(payload)
  828. }
  829. return fsConfig
  830. }
  831. func getSFTPFsFromTemplate(fsConfig vfs.SFTPFsConfig, replacements map[string]string) vfs.SFTPFsConfig {
  832. fsConfig.Prefix = replacePlaceholders(fsConfig.Prefix, replacements)
  833. fsConfig.Username = replacePlaceholders(fsConfig.Username, replacements)
  834. if fsConfig.Password != nil && fsConfig.Password.IsPlain() {
  835. payload := replacePlaceholders(fsConfig.Password.GetPayload(), replacements)
  836. fsConfig.Password = kms.NewPlainSecret(payload)
  837. }
  838. return fsConfig
  839. }
  840. func getUserFromTemplate(user dataprovider.User, template userTemplateFields) dataprovider.User {
  841. user.Username = template.Username
  842. user.Password = template.Password
  843. user.PublicKeys = nil
  844. if template.PublicKey != "" {
  845. user.PublicKeys = append(user.PublicKeys, template.PublicKey)
  846. }
  847. replacements := make(map[string]string)
  848. replacements["%username%"] = user.Username
  849. user.Password = replacePlaceholders(user.Password, replacements)
  850. replacements["%password%"] = user.Password
  851. user.HomeDir = replacePlaceholders(user.HomeDir, replacements)
  852. var vfolders []vfs.VirtualFolder
  853. for _, vfolder := range user.VirtualFolders {
  854. vfolder.Name = replacePlaceholders(vfolder.Name, replacements)
  855. vfolder.VirtualPath = replacePlaceholders(vfolder.VirtualPath, replacements)
  856. vfolders = append(vfolders, vfolder)
  857. }
  858. user.VirtualFolders = vfolders
  859. user.Description = replacePlaceholders(user.Description, replacements)
  860. user.AdditionalInfo = replacePlaceholders(user.AdditionalInfo, replacements)
  861. switch user.FsConfig.Provider {
  862. case sdk.CryptedFilesystemProvider:
  863. user.FsConfig.CryptConfig = getCryptFsFromTemplate(user.FsConfig.CryptConfig, replacements)
  864. case sdk.S3FilesystemProvider:
  865. user.FsConfig.S3Config = getS3FsFromTemplate(user.FsConfig.S3Config, replacements)
  866. case sdk.GCSFilesystemProvider:
  867. user.FsConfig.GCSConfig = getGCSFsFromTemplate(user.FsConfig.GCSConfig, replacements)
  868. case sdk.AzureBlobFilesystemProvider:
  869. user.FsConfig.AzBlobConfig = getAzBlobFsFromTemplate(user.FsConfig.AzBlobConfig, replacements)
  870. case sdk.SFTPFilesystemProvider:
  871. user.FsConfig.SFTPConfig = getSFTPFsFromTemplate(user.FsConfig.SFTPConfig, replacements)
  872. }
  873. return user
  874. }
  875. func getUserFromPostFields(r *http.Request) (dataprovider.User, error) {
  876. var user dataprovider.User
  877. err := r.ParseMultipartForm(maxRequestSize)
  878. if err != nil {
  879. return user, err
  880. }
  881. uid, err := strconv.Atoi(r.Form.Get("uid"))
  882. if err != nil {
  883. return user, err
  884. }
  885. gid, err := strconv.Atoi(r.Form.Get("gid"))
  886. if err != nil {
  887. return user, err
  888. }
  889. maxSessions, err := strconv.Atoi(r.Form.Get("max_sessions"))
  890. if err != nil {
  891. return user, err
  892. }
  893. quotaSize, err := strconv.ParseInt(r.Form.Get("quota_size"), 10, 64)
  894. if err != nil {
  895. return user, err
  896. }
  897. quotaFiles, err := strconv.Atoi(r.Form.Get("quota_files"))
  898. if err != nil {
  899. return user, err
  900. }
  901. bandwidthUL, err := strconv.ParseInt(r.Form.Get("upload_bandwidth"), 10, 64)
  902. if err != nil {
  903. return user, err
  904. }
  905. bandwidthDL, err := strconv.ParseInt(r.Form.Get("download_bandwidth"), 10, 64)
  906. if err != nil {
  907. return user, err
  908. }
  909. status, err := strconv.Atoi(r.Form.Get("status"))
  910. if err != nil {
  911. return user, err
  912. }
  913. expirationDateMillis := int64(0)
  914. expirationDateString := r.Form.Get("expiration_date")
  915. if len(strings.TrimSpace(expirationDateString)) > 0 {
  916. expirationDate, err := time.Parse(webDateTimeFormat, expirationDateString)
  917. if err != nil {
  918. return user, err
  919. }
  920. expirationDateMillis = util.GetTimeAsMsSinceEpoch(expirationDate)
  921. }
  922. fsConfig, err := getFsConfigFromPostFields(r)
  923. if err != nil {
  924. return user, err
  925. }
  926. user = dataprovider.User{
  927. BaseUser: sdk.BaseUser{
  928. Username: r.Form.Get("username"),
  929. Password: r.Form.Get("password"),
  930. PublicKeys: r.Form["public_keys"],
  931. HomeDir: r.Form.Get("home_dir"),
  932. UID: uid,
  933. GID: gid,
  934. Permissions: getUserPermissionsFromPostFields(r),
  935. MaxSessions: maxSessions,
  936. QuotaSize: quotaSize,
  937. QuotaFiles: quotaFiles,
  938. UploadBandwidth: bandwidthUL,
  939. DownloadBandwidth: bandwidthDL,
  940. Status: status,
  941. ExpirationDate: expirationDateMillis,
  942. Filters: getFiltersFromUserPostFields(r),
  943. AdditionalInfo: r.Form.Get("additional_info"),
  944. Description: r.Form.Get("description"),
  945. },
  946. VirtualFolders: getVirtualFoldersFromPostFields(r),
  947. FsConfig: fsConfig,
  948. }
  949. maxFileSize, err := strconv.ParseInt(r.Form.Get("max_upload_file_size"), 10, 64)
  950. user.Filters.MaxUploadFileSize = maxFileSize
  951. return user, err
  952. }
  953. func handleWebAdminCredentials(w http.ResponseWriter, r *http.Request) {
  954. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  955. renderCredentialsPage(w, r, "", "")
  956. }
  957. func handleWebAdminChangePwdPost(w http.ResponseWriter, r *http.Request) {
  958. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  959. err := r.ParseForm()
  960. if err != nil {
  961. renderCredentialsPage(w, r, err.Error(), "")
  962. return
  963. }
  964. if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
  965. renderForbiddenPage(w, r, err.Error())
  966. return
  967. }
  968. err = doChangeAdminPassword(r, r.Form.Get("current_password"), r.Form.Get("new_password1"),
  969. r.Form.Get("new_password2"))
  970. if err != nil {
  971. renderCredentialsPage(w, r, err.Error(), "")
  972. return
  973. }
  974. handleWebLogout(w, r)
  975. }
  976. func handleWebAdminManageAPIKeyPost(w http.ResponseWriter, r *http.Request) {
  977. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  978. err := r.ParseForm()
  979. if err != nil {
  980. renderCredentialsPage(w, r, err.Error(), "")
  981. return
  982. }
  983. if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
  984. renderForbiddenPage(w, r, err.Error())
  985. return
  986. }
  987. claims, err := getTokenClaims(r)
  988. if err != nil || claims.Username == "" {
  989. renderCredentialsPage(w, r, "", "Invalid token claims")
  990. return
  991. }
  992. admin, err := dataprovider.AdminExists(claims.Username)
  993. if err != nil {
  994. renderCredentialsPage(w, r, "", err.Error())
  995. return
  996. }
  997. admin.Filters.AllowAPIKeyAuth = len(r.Form.Get("allow_api_key_auth")) > 0
  998. err = dataprovider.UpdateAdmin(&admin)
  999. if err != nil {
  1000. renderCredentialsPage(w, r, "", err.Error())
  1001. return
  1002. }
  1003. renderMessagePage(w, r, "API key authentication updated", "", http.StatusOK, nil,
  1004. "Your API key access permission has been successfully updated")
  1005. }
  1006. func handleWebLogout(w http.ResponseWriter, r *http.Request) {
  1007. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1008. c := jwtTokenClaims{}
  1009. c.removeCookie(w, r, webBaseAdminPath)
  1010. http.Redirect(w, r, webLoginPath, http.StatusFound)
  1011. }
  1012. func handleWebMaintenance(w http.ResponseWriter, r *http.Request) {
  1013. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1014. renderMaintenancePage(w, r, "")
  1015. }
  1016. func handleWebRestore(w http.ResponseWriter, r *http.Request) {
  1017. r.Body = http.MaxBytesReader(w, r.Body, MaxRestoreSize)
  1018. err := r.ParseMultipartForm(MaxRestoreSize)
  1019. if err != nil {
  1020. renderMaintenancePage(w, r, err.Error())
  1021. return
  1022. }
  1023. if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
  1024. renderForbiddenPage(w, r, err.Error())
  1025. return
  1026. }
  1027. restoreMode, err := strconv.Atoi(r.Form.Get("mode"))
  1028. if err != nil {
  1029. renderMaintenancePage(w, r, err.Error())
  1030. return
  1031. }
  1032. scanQuota, err := strconv.Atoi(r.Form.Get("quota"))
  1033. if err != nil {
  1034. renderMaintenancePage(w, r, err.Error())
  1035. return
  1036. }
  1037. backupFile, _, err := r.FormFile("backup_file")
  1038. if err != nil {
  1039. renderMaintenancePage(w, r, err.Error())
  1040. return
  1041. }
  1042. defer backupFile.Close()
  1043. backupContent, err := io.ReadAll(backupFile)
  1044. if err != nil || len(backupContent) == 0 {
  1045. if len(backupContent) == 0 {
  1046. err = errors.New("backup file size must be greater than 0")
  1047. }
  1048. renderMaintenancePage(w, r, err.Error())
  1049. return
  1050. }
  1051. if err := restoreBackup(backupContent, "", scanQuota, restoreMode); err != nil {
  1052. renderMaintenancePage(w, r, err.Error())
  1053. return
  1054. }
  1055. renderMessagePage(w, r, "Data restored", "", http.StatusOK, nil, "Your backup was successfully restored")
  1056. }
  1057. func handleGetWebAdmins(w http.ResponseWriter, r *http.Request) {
  1058. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1059. limit := defaultQueryLimit
  1060. if _, ok := r.URL.Query()["qlimit"]; ok {
  1061. var err error
  1062. limit, err = strconv.Atoi(r.URL.Query().Get("qlimit"))
  1063. if err != nil {
  1064. limit = defaultQueryLimit
  1065. }
  1066. }
  1067. admins := make([]dataprovider.Admin, 0, limit)
  1068. for {
  1069. a, err := dataprovider.GetAdmins(limit, len(admins), dataprovider.OrderASC)
  1070. if err != nil {
  1071. renderInternalServerErrorPage(w, r, err)
  1072. return
  1073. }
  1074. admins = append(admins, a...)
  1075. if len(a) < limit {
  1076. break
  1077. }
  1078. }
  1079. data := adminsPage{
  1080. basePage: getBasePageData(pageAdminsTitle, webAdminsPath, r),
  1081. Admins: admins,
  1082. }
  1083. renderAdminTemplate(w, templateAdmins, data)
  1084. }
  1085. func handleWebAdminSetupGet(w http.ResponseWriter, r *http.Request) {
  1086. r.Body = http.MaxBytesReader(w, r.Body, maxLoginBodySize)
  1087. if dataprovider.HasAdmin() {
  1088. http.Redirect(w, r, webLoginPath, http.StatusFound)
  1089. return
  1090. }
  1091. renderAdminSetupPage(w, r, "", "")
  1092. }
  1093. func handleWebAddAdminGet(w http.ResponseWriter, r *http.Request) {
  1094. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1095. admin := &dataprovider.Admin{Status: 1}
  1096. renderAddUpdateAdminPage(w, r, admin, "", true)
  1097. }
  1098. func handleWebUpdateAdminGet(w http.ResponseWriter, r *http.Request) {
  1099. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1100. username := getURLParam(r, "username")
  1101. admin, err := dataprovider.AdminExists(username)
  1102. if err == nil {
  1103. renderAddUpdateAdminPage(w, r, &admin, "", false)
  1104. } else if _, ok := err.(*util.RecordNotFoundError); ok {
  1105. renderNotFoundPage(w, r, err)
  1106. } else {
  1107. renderInternalServerErrorPage(w, r, err)
  1108. }
  1109. }
  1110. func handleWebAddAdminPost(w http.ResponseWriter, r *http.Request) {
  1111. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1112. admin, err := getAdminFromPostFields(r)
  1113. if err != nil {
  1114. renderAddUpdateAdminPage(w, r, &admin, err.Error(), true)
  1115. return
  1116. }
  1117. if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
  1118. renderForbiddenPage(w, r, err.Error())
  1119. return
  1120. }
  1121. err = dataprovider.AddAdmin(&admin)
  1122. if err != nil {
  1123. renderAddUpdateAdminPage(w, r, &admin, err.Error(), true)
  1124. return
  1125. }
  1126. http.Redirect(w, r, webAdminsPath, http.StatusSeeOther)
  1127. }
  1128. func handleWebUpdateAdminPost(w http.ResponseWriter, r *http.Request) {
  1129. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1130. username := getURLParam(r, "username")
  1131. admin, err := dataprovider.AdminExists(username)
  1132. if _, ok := err.(*util.RecordNotFoundError); ok {
  1133. renderNotFoundPage(w, r, err)
  1134. return
  1135. } else if err != nil {
  1136. renderInternalServerErrorPage(w, r, err)
  1137. return
  1138. }
  1139. updatedAdmin, err := getAdminFromPostFields(r)
  1140. if err != nil {
  1141. renderAddUpdateAdminPage(w, r, &updatedAdmin, err.Error(), false)
  1142. return
  1143. }
  1144. if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
  1145. renderForbiddenPage(w, r, err.Error())
  1146. return
  1147. }
  1148. updatedAdmin.ID = admin.ID
  1149. updatedAdmin.Username = admin.Username
  1150. if updatedAdmin.Password == "" {
  1151. updatedAdmin.Password = admin.Password
  1152. }
  1153. claims, err := getTokenClaims(r)
  1154. if err != nil || claims.Username == "" {
  1155. renderAddUpdateAdminPage(w, r, &updatedAdmin, fmt.Sprintf("Invalid token claims: %v", err), false)
  1156. return
  1157. }
  1158. if username == claims.Username {
  1159. if claims.isCriticalPermRemoved(updatedAdmin.Permissions) {
  1160. renderAddUpdateAdminPage(w, r, &updatedAdmin, "You cannot remove these permissions to yourself", false)
  1161. return
  1162. }
  1163. if updatedAdmin.Status == 0 {
  1164. renderAddUpdateAdminPage(w, r, &updatedAdmin, "You cannot disable yourself", false)
  1165. return
  1166. }
  1167. }
  1168. err = dataprovider.UpdateAdmin(&updatedAdmin)
  1169. if err != nil {
  1170. renderAddUpdateAdminPage(w, r, &admin, err.Error(), false)
  1171. return
  1172. }
  1173. http.Redirect(w, r, webAdminsPath, http.StatusSeeOther)
  1174. }
  1175. func handleWebDefenderPage(w http.ResponseWriter, r *http.Request) {
  1176. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1177. data := defenderHostsPage{
  1178. basePage: getBasePageData(pageDefenderTitle, webDefenderPath, r),
  1179. DefenderHostsURL: webDefenderHostsPath,
  1180. }
  1181. renderAdminTemplate(w, templateDefender, data)
  1182. }
  1183. func handleGetWebUsers(w http.ResponseWriter, r *http.Request) {
  1184. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1185. limit := defaultQueryLimit
  1186. if _, ok := r.URL.Query()["qlimit"]; ok {
  1187. var err error
  1188. limit, err = strconv.Atoi(r.URL.Query().Get("qlimit"))
  1189. if err != nil {
  1190. limit = defaultQueryLimit
  1191. }
  1192. }
  1193. users := make([]dataprovider.User, 0, limit)
  1194. for {
  1195. u, err := dataprovider.GetUsers(limit, len(users), dataprovider.OrderASC)
  1196. if err != nil {
  1197. renderInternalServerErrorPage(w, r, err)
  1198. return
  1199. }
  1200. users = append(users, u...)
  1201. if len(u) < limit {
  1202. break
  1203. }
  1204. }
  1205. data := usersPage{
  1206. basePage: getBasePageData(pageUsersTitle, webUsersPath, r),
  1207. Users: users,
  1208. }
  1209. renderAdminTemplate(w, templateUsers, data)
  1210. }
  1211. func handleWebTemplateFolderGet(w http.ResponseWriter, r *http.Request) {
  1212. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1213. if r.URL.Query().Get("from") != "" {
  1214. name := r.URL.Query().Get("from")
  1215. folder, err := dataprovider.GetFolderByName(name)
  1216. if err == nil {
  1217. renderFolderPage(w, r, folder, folderPageModeTemplate, "")
  1218. } else if _, ok := err.(*util.RecordNotFoundError); ok {
  1219. renderNotFoundPage(w, r, err)
  1220. } else {
  1221. renderInternalServerErrorPage(w, r, err)
  1222. }
  1223. } else {
  1224. folder := vfs.BaseVirtualFolder{}
  1225. renderFolderPage(w, r, folder, folderPageModeTemplate, "")
  1226. }
  1227. }
  1228. func handleWebTemplateFolderPost(w http.ResponseWriter, r *http.Request) {
  1229. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1230. templateFolder := vfs.BaseVirtualFolder{}
  1231. err := r.ParseMultipartForm(maxRequestSize)
  1232. if err != nil {
  1233. renderMessagePage(w, r, "Error parsing folders fields", "", http.StatusBadRequest, err, "")
  1234. return
  1235. }
  1236. if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
  1237. renderForbiddenPage(w, r, err.Error())
  1238. return
  1239. }
  1240. templateFolder.MappedPath = r.Form.Get("mapped_path")
  1241. templateFolder.Description = r.Form.Get("description")
  1242. fsConfig, err := getFsConfigFromPostFields(r)
  1243. if err != nil {
  1244. renderMessagePage(w, r, "Error parsing folders fields", "", http.StatusBadRequest, err, "")
  1245. return
  1246. }
  1247. templateFolder.FsConfig = fsConfig
  1248. var dump dataprovider.BackupData
  1249. dump.Version = dataprovider.DumpVersion
  1250. foldersFields := getFoldersForTemplate(r)
  1251. for _, tmpl := range foldersFields {
  1252. f := getFolderFromTemplate(templateFolder, tmpl)
  1253. if err := dataprovider.ValidateFolder(&f); err != nil {
  1254. renderMessagePage(w, r, fmt.Sprintf("Error validating folder %#v", f.Name), "", http.StatusBadRequest, err, "")
  1255. return
  1256. }
  1257. dump.Folders = append(dump.Folders, f)
  1258. }
  1259. if len(dump.Folders) == 0 {
  1260. renderMessagePage(w, r, "No folders to export", "No valid folders found, export is not possible", http.StatusBadRequest, nil, "")
  1261. return
  1262. }
  1263. w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"sftpgo-%v-folders-from-template.json\"", len(dump.Folders)))
  1264. render.JSON(w, r, dump)
  1265. }
  1266. func handleWebTemplateUserGet(w http.ResponseWriter, r *http.Request) {
  1267. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1268. if r.URL.Query().Get("from") != "" {
  1269. username := r.URL.Query().Get("from")
  1270. user, err := dataprovider.UserExists(username)
  1271. if err == nil {
  1272. user.SetEmptySecrets()
  1273. renderUserPage(w, r, &user, userPageModeTemplate, "")
  1274. } else if _, ok := err.(*util.RecordNotFoundError); ok {
  1275. renderNotFoundPage(w, r, err)
  1276. } else {
  1277. renderInternalServerErrorPage(w, r, err)
  1278. }
  1279. } else {
  1280. user := dataprovider.User{BaseUser: sdk.BaseUser{Status: 1}}
  1281. renderUserPage(w, r, &user, userPageModeTemplate, "")
  1282. }
  1283. }
  1284. func handleWebTemplateUserPost(w http.ResponseWriter, r *http.Request) {
  1285. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1286. templateUser, err := getUserFromPostFields(r)
  1287. if err != nil {
  1288. renderMessagePage(w, r, "Error parsing user fields", "", http.StatusBadRequest, err, "")
  1289. return
  1290. }
  1291. if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
  1292. renderForbiddenPage(w, r, err.Error())
  1293. return
  1294. }
  1295. var dump dataprovider.BackupData
  1296. dump.Version = dataprovider.DumpVersion
  1297. userTmplFields := getUsersForTemplate(r)
  1298. for _, tmpl := range userTmplFields {
  1299. u := getUserFromTemplate(templateUser, tmpl)
  1300. if err := dataprovider.ValidateUser(&u); err != nil {
  1301. renderMessagePage(w, r, fmt.Sprintf("Error validating user %#v", u.Username), "", http.StatusBadRequest, err, "")
  1302. return
  1303. }
  1304. dump.Users = append(dump.Users, u)
  1305. for _, folder := range u.VirtualFolders {
  1306. if !dump.HasFolder(folder.Name) {
  1307. dump.Folders = append(dump.Folders, folder.BaseVirtualFolder)
  1308. }
  1309. }
  1310. }
  1311. if len(dump.Users) == 0 {
  1312. renderMessagePage(w, r, "No users to export", "No valid users found, export is not possible", http.StatusBadRequest, nil, "")
  1313. return
  1314. }
  1315. w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"sftpgo-%v-users-from-template.json\"", len(dump.Users)))
  1316. render.JSON(w, r, dump)
  1317. }
  1318. func handleWebAddUserGet(w http.ResponseWriter, r *http.Request) {
  1319. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1320. if r.URL.Query().Get("clone-from") != "" {
  1321. username := r.URL.Query().Get("clone-from")
  1322. user, err := dataprovider.UserExists(username)
  1323. if err == nil {
  1324. user.ID = 0
  1325. user.Username = ""
  1326. user.Password = ""
  1327. user.SetEmptySecrets()
  1328. renderUserPage(w, r, &user, userPageModeAdd, "")
  1329. } else if _, ok := err.(*util.RecordNotFoundError); ok {
  1330. renderNotFoundPage(w, r, err)
  1331. } else {
  1332. renderInternalServerErrorPage(w, r, err)
  1333. }
  1334. } else {
  1335. user := dataprovider.User{BaseUser: sdk.BaseUser{Status: 1}}
  1336. renderUserPage(w, r, &user, userPageModeAdd, "")
  1337. }
  1338. }
  1339. func handleWebUpdateUserGet(w http.ResponseWriter, r *http.Request) {
  1340. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1341. username := getURLParam(r, "username")
  1342. user, err := dataprovider.UserExists(username)
  1343. if err == nil {
  1344. renderUserPage(w, r, &user, userPageModeUpdate, "")
  1345. } else if _, ok := err.(*util.RecordNotFoundError); ok {
  1346. renderNotFoundPage(w, r, err)
  1347. } else {
  1348. renderInternalServerErrorPage(w, r, err)
  1349. }
  1350. }
  1351. func handleWebAddUserPost(w http.ResponseWriter, r *http.Request) {
  1352. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1353. user, err := getUserFromPostFields(r)
  1354. if err != nil {
  1355. renderUserPage(w, r, &user, userPageModeAdd, err.Error())
  1356. return
  1357. }
  1358. if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
  1359. renderForbiddenPage(w, r, err.Error())
  1360. return
  1361. }
  1362. err = dataprovider.AddUser(&user)
  1363. if err == nil {
  1364. http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
  1365. } else {
  1366. renderUserPage(w, r, &user, userPageModeAdd, err.Error())
  1367. }
  1368. }
  1369. func handleWebUpdateUserPost(w http.ResponseWriter, r *http.Request) {
  1370. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1371. username := getURLParam(r, "username")
  1372. user, err := dataprovider.UserExists(username)
  1373. if _, ok := err.(*util.RecordNotFoundError); ok {
  1374. renderNotFoundPage(w, r, err)
  1375. return
  1376. } else if err != nil {
  1377. renderInternalServerErrorPage(w, r, err)
  1378. return
  1379. }
  1380. updatedUser, err := getUserFromPostFields(r)
  1381. if err != nil {
  1382. renderUserPage(w, r, &user, userPageModeUpdate, err.Error())
  1383. return
  1384. }
  1385. if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
  1386. renderForbiddenPage(w, r, err.Error())
  1387. return
  1388. }
  1389. updatedUser.ID = user.ID
  1390. updatedUser.Username = user.Username
  1391. updatedUser.SetEmptySecretsIfNil()
  1392. if updatedUser.Password == redactedSecret {
  1393. updatedUser.Password = user.Password
  1394. }
  1395. updateEncryptedSecrets(&updatedUser.FsConfig, user.FsConfig.S3Config.AccessSecret, user.FsConfig.AzBlobConfig.AccountKey,
  1396. user.FsConfig.AzBlobConfig.SASURL, user.FsConfig.GCSConfig.Credentials, user.FsConfig.CryptConfig.Passphrase,
  1397. user.FsConfig.SFTPConfig.Password, user.FsConfig.SFTPConfig.PrivateKey)
  1398. err = dataprovider.UpdateUser(&updatedUser)
  1399. if err == nil {
  1400. if len(r.Form.Get("disconnect")) > 0 {
  1401. disconnectUser(user.Username)
  1402. }
  1403. http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
  1404. } else {
  1405. renderUserPage(w, r, &user, userPageModeUpdate, err.Error())
  1406. }
  1407. }
  1408. func handleWebGetStatus(w http.ResponseWriter, r *http.Request) {
  1409. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1410. data := statusPage{
  1411. basePage: getBasePageData(pageStatusTitle, webStatusPath, r),
  1412. Status: getServicesStatus(),
  1413. }
  1414. renderAdminTemplate(w, templateStatus, data)
  1415. }
  1416. func handleWebGetConnections(w http.ResponseWriter, r *http.Request) {
  1417. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1418. connectionStats := common.Connections.GetStats()
  1419. data := connectionsPage{
  1420. basePage: getBasePageData(pageConnectionsTitle, webConnectionsPath, r),
  1421. Connections: connectionStats,
  1422. }
  1423. renderAdminTemplate(w, templateConnections, data)
  1424. }
  1425. func handleWebAddFolderGet(w http.ResponseWriter, r *http.Request) {
  1426. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1427. renderFolderPage(w, r, vfs.BaseVirtualFolder{}, folderPageModeAdd, "")
  1428. }
  1429. func handleWebAddFolderPost(w http.ResponseWriter, r *http.Request) {
  1430. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1431. folder := vfs.BaseVirtualFolder{}
  1432. err := r.ParseMultipartForm(maxRequestSize)
  1433. if err != nil {
  1434. renderFolderPage(w, r, folder, folderPageModeAdd, err.Error())
  1435. return
  1436. }
  1437. if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
  1438. renderForbiddenPage(w, r, err.Error())
  1439. return
  1440. }
  1441. folder.MappedPath = r.Form.Get("mapped_path")
  1442. folder.Name = r.Form.Get("name")
  1443. folder.Description = r.Form.Get("description")
  1444. fsConfig, err := getFsConfigFromPostFields(r)
  1445. if err != nil {
  1446. renderFolderPage(w, r, folder, folderPageModeAdd, err.Error())
  1447. return
  1448. }
  1449. folder.FsConfig = fsConfig
  1450. err = dataprovider.AddFolder(&folder)
  1451. if err == nil {
  1452. http.Redirect(w, r, webFoldersPath, http.StatusSeeOther)
  1453. } else {
  1454. renderFolderPage(w, r, folder, folderPageModeAdd, err.Error())
  1455. }
  1456. }
  1457. func handleWebUpdateFolderGet(w http.ResponseWriter, r *http.Request) {
  1458. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1459. name := getURLParam(r, "name")
  1460. folder, err := dataprovider.GetFolderByName(name)
  1461. if err == nil {
  1462. renderFolderPage(w, r, folder, folderPageModeUpdate, "")
  1463. } else if _, ok := err.(*util.RecordNotFoundError); ok {
  1464. renderNotFoundPage(w, r, err)
  1465. } else {
  1466. renderInternalServerErrorPage(w, r, err)
  1467. }
  1468. }
  1469. func handleWebUpdateFolderPost(w http.ResponseWriter, r *http.Request) {
  1470. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1471. name := getURLParam(r, "name")
  1472. folder, err := dataprovider.GetFolderByName(name)
  1473. if _, ok := err.(*util.RecordNotFoundError); ok {
  1474. renderNotFoundPage(w, r, err)
  1475. return
  1476. } else if err != nil {
  1477. renderInternalServerErrorPage(w, r, err)
  1478. return
  1479. }
  1480. err = r.ParseMultipartForm(maxRequestSize)
  1481. if err != nil {
  1482. renderFolderPage(w, r, folder, folderPageModeUpdate, err.Error())
  1483. return
  1484. }
  1485. if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
  1486. renderForbiddenPage(w, r, err.Error())
  1487. return
  1488. }
  1489. fsConfig, err := getFsConfigFromPostFields(r)
  1490. if err != nil {
  1491. renderFolderPage(w, r, folder, folderPageModeUpdate, err.Error())
  1492. return
  1493. }
  1494. updatedFolder := &vfs.BaseVirtualFolder{
  1495. MappedPath: r.Form.Get("mapped_path"),
  1496. Description: r.Form.Get("description"),
  1497. }
  1498. updatedFolder.ID = folder.ID
  1499. updatedFolder.Name = folder.Name
  1500. updatedFolder.FsConfig = fsConfig
  1501. updatedFolder.FsConfig.SetEmptySecretsIfNil()
  1502. updateEncryptedSecrets(&updatedFolder.FsConfig, folder.FsConfig.S3Config.AccessSecret, folder.FsConfig.AzBlobConfig.AccountKey,
  1503. folder.FsConfig.AzBlobConfig.SASURL, folder.FsConfig.GCSConfig.Credentials, folder.FsConfig.CryptConfig.Passphrase,
  1504. folder.FsConfig.SFTPConfig.Password, folder.FsConfig.SFTPConfig.PrivateKey)
  1505. err = dataprovider.UpdateFolder(updatedFolder, folder.Users)
  1506. if err != nil {
  1507. renderFolderPage(w, r, folder, folderPageModeUpdate, err.Error())
  1508. return
  1509. }
  1510. http.Redirect(w, r, webFoldersPath, http.StatusSeeOther)
  1511. }
  1512. func getWebVirtualFolders(w http.ResponseWriter, r *http.Request, limit int) ([]vfs.BaseVirtualFolder, error) {
  1513. folders := make([]vfs.BaseVirtualFolder, 0, limit)
  1514. for {
  1515. f, err := dataprovider.GetFolders(limit, len(folders), dataprovider.OrderASC)
  1516. if err != nil {
  1517. renderInternalServerErrorPage(w, r, err)
  1518. return folders, err
  1519. }
  1520. folders = append(folders, f...)
  1521. if len(f) < limit {
  1522. break
  1523. }
  1524. }
  1525. return folders, nil
  1526. }
  1527. func handleWebGetFolders(w http.ResponseWriter, r *http.Request) {
  1528. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1529. limit := defaultQueryLimit
  1530. if _, ok := r.URL.Query()["qlimit"]; ok {
  1531. var err error
  1532. limit, err = strconv.Atoi(r.URL.Query().Get("qlimit"))
  1533. if err != nil {
  1534. limit = defaultQueryLimit
  1535. }
  1536. }
  1537. folders, err := getWebVirtualFolders(w, r, limit)
  1538. if err != nil {
  1539. return
  1540. }
  1541. data := foldersPage{
  1542. basePage: getBasePageData(pageFoldersTitle, webFoldersPath, r),
  1543. Folders: folders,
  1544. }
  1545. renderAdminTemplate(w, templateFolders, data)
  1546. }