webclient.go 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240
  1. package httpd
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "html/template"
  8. "io"
  9. "net/http"
  10. "net/url"
  11. "os"
  12. "path"
  13. "path/filepath"
  14. "strconv"
  15. "strings"
  16. "time"
  17. "github.com/go-chi/render"
  18. "github.com/rs/xid"
  19. "github.com/sftpgo/sdk"
  20. "github.com/drakkan/sftpgo/v2/common"
  21. "github.com/drakkan/sftpgo/v2/dataprovider"
  22. "github.com/drakkan/sftpgo/v2/mfa"
  23. "github.com/drakkan/sftpgo/v2/smtp"
  24. "github.com/drakkan/sftpgo/v2/util"
  25. "github.com/drakkan/sftpgo/v2/version"
  26. "github.com/drakkan/sftpgo/v2/vfs"
  27. )
  28. const (
  29. templateClientDir = "webclient"
  30. templateClientBase = "base.html"
  31. templateClientBaseLogin = "baselogin.html"
  32. templateClientLogin = "login.html"
  33. templateClientFiles = "files.html"
  34. templateClientMessage = "message.html"
  35. templateClientProfile = "profile.html"
  36. templateClientChangePwd = "changepassword.html"
  37. templateClientTwoFactor = "twofactor.html"
  38. templateClientTwoFactorRecovery = "twofactor-recovery.html"
  39. templateClientMFA = "mfa.html"
  40. templateClientEditFile = "editfile.html"
  41. templateClientShare = "share.html"
  42. templateClientShares = "shares.html"
  43. templateClientViewPDF = "viewpdf.html"
  44. templateShareFiles = "sharefiles.html"
  45. templateUploadToShare = "shareupload.html"
  46. pageClientFilesTitle = "My Files"
  47. pageClientSharesTitle = "Shares"
  48. pageClientProfileTitle = "My Profile"
  49. pageClientChangePwdTitle = "Change password"
  50. pageClient2FATitle = "Two-factor auth"
  51. pageClientEditFileTitle = "Edit file"
  52. pageClientForgotPwdTitle = "SFTPGo WebClient - Forgot password"
  53. pageClientResetPwdTitle = "SFTPGo WebClient - Reset password"
  54. pageExtShareTitle = "Shared files"
  55. pageUploadToShareTitle = "Upload to share"
  56. )
  57. // condResult is the result of an HTTP request precondition check.
  58. // See https://tools.ietf.org/html/rfc7232 section 3.
  59. type condResult int
  60. const (
  61. condNone condResult = iota
  62. condTrue
  63. condFalse
  64. )
  65. var (
  66. clientTemplates = make(map[string]*template.Template)
  67. unixEpochTime = time.Unix(0, 0)
  68. )
  69. // isZeroTime reports whether t is obviously unspecified (either zero or Unix()=0).
  70. func isZeroTime(t time.Time) bool {
  71. return t.IsZero() || t.Equal(unixEpochTime)
  72. }
  73. type baseClientPage struct {
  74. Title string
  75. CurrentURL string
  76. FilesURL string
  77. SharesURL string
  78. ShareURL string
  79. ProfileURL string
  80. ChangePwdURL string
  81. StaticURL string
  82. LogoutURL string
  83. MFAURL string
  84. MFATitle string
  85. FilesTitle string
  86. SharesTitle string
  87. ProfileTitle string
  88. Version string
  89. CSRFToken string
  90. HasExternalLogin bool
  91. LoggedUser *dataprovider.User
  92. ExtraCSS []CustomCSS
  93. }
  94. type dirMapping struct {
  95. DirName string
  96. Href string
  97. }
  98. type viewPDFPage struct {
  99. Title string
  100. URL string
  101. StaticURL string
  102. ExtraCSS []CustomCSS
  103. }
  104. type editFilePage struct {
  105. baseClientPage
  106. CurrentDir string
  107. FileURL string
  108. Path string
  109. Name string
  110. ReadOnly bool
  111. Data string
  112. }
  113. type filesPage struct {
  114. baseClientPage
  115. CurrentDir string
  116. DirsURL string
  117. DownloadURL string
  118. ViewPDFURL string
  119. FileURL string
  120. CanAddFiles bool
  121. CanCreateDirs bool
  122. CanRename bool
  123. CanDelete bool
  124. CanDownload bool
  125. CanShare bool
  126. Error string
  127. Paths []dirMapping
  128. HasIntegrations bool
  129. }
  130. type shareFilesPage struct {
  131. baseClientPage
  132. CurrentDir string
  133. DirsURL string
  134. FilesURL string
  135. DownloadURL string
  136. Error string
  137. Paths []dirMapping
  138. }
  139. type shareUploadPage struct {
  140. baseClientPage
  141. Share *dataprovider.Share
  142. UploadBasePath string
  143. }
  144. type clientMessagePage struct {
  145. baseClientPage
  146. Error string
  147. Success string
  148. }
  149. type clientProfilePage struct {
  150. baseClientPage
  151. PublicKeys []string
  152. CanSubmit bool
  153. AllowAPIKeyAuth bool
  154. Email string
  155. Description string
  156. Error string
  157. }
  158. type changeClientPasswordPage struct {
  159. baseClientPage
  160. Error string
  161. }
  162. type clientMFAPage struct {
  163. baseClientPage
  164. TOTPConfigs []string
  165. TOTPConfig dataprovider.UserTOTPConfig
  166. GenerateTOTPURL string
  167. ValidateTOTPURL string
  168. SaveTOTPURL string
  169. RecCodesURL string
  170. Protocols []string
  171. }
  172. type clientSharesPage struct {
  173. baseClientPage
  174. Shares []dataprovider.Share
  175. BasePublicSharesURL string
  176. }
  177. type clientSharePage struct {
  178. baseClientPage
  179. Share *dataprovider.Share
  180. Error string
  181. IsAdd bool
  182. }
  183. func getFileObjectURL(baseDir, name, baseWebPath string) string {
  184. return fmt.Sprintf("%v?path=%v&_=%v", baseWebPath, url.QueryEscape(path.Join(baseDir, name)), time.Now().UTC().Unix())
  185. }
  186. func getFileObjectModTime(t time.Time) string {
  187. if isZeroTime(t) {
  188. return ""
  189. }
  190. return t.Format("2006-01-02 15:04")
  191. }
  192. func loadClientTemplates(templatesPath string) {
  193. filesPaths := []string{
  194. filepath.Join(templatesPath, templateClientDir, templateClientBase),
  195. filepath.Join(templatesPath, templateClientDir, templateClientFiles),
  196. }
  197. editFilePath := []string{
  198. filepath.Join(templatesPath, templateClientDir, templateClientBase),
  199. filepath.Join(templatesPath, templateClientDir, templateClientEditFile),
  200. }
  201. sharesPaths := []string{
  202. filepath.Join(templatesPath, templateClientDir, templateClientBase),
  203. filepath.Join(templatesPath, templateClientDir, templateClientShares),
  204. }
  205. sharePaths := []string{
  206. filepath.Join(templatesPath, templateClientDir, templateClientBase),
  207. filepath.Join(templatesPath, templateClientDir, templateClientShare),
  208. }
  209. profilePaths := []string{
  210. filepath.Join(templatesPath, templateClientDir, templateClientBase),
  211. filepath.Join(templatesPath, templateClientDir, templateClientProfile),
  212. }
  213. changePwdPaths := []string{
  214. filepath.Join(templatesPath, templateClientDir, templateClientBase),
  215. filepath.Join(templatesPath, templateClientDir, templateClientChangePwd),
  216. }
  217. loginPath := []string{
  218. filepath.Join(templatesPath, templateClientDir, templateClientBaseLogin),
  219. filepath.Join(templatesPath, templateClientDir, templateClientLogin),
  220. }
  221. messagePath := []string{
  222. filepath.Join(templatesPath, templateClientDir, templateClientBase),
  223. filepath.Join(templatesPath, templateClientDir, templateClientMessage),
  224. }
  225. mfaPath := []string{
  226. filepath.Join(templatesPath, templateClientDir, templateClientBase),
  227. filepath.Join(templatesPath, templateClientDir, templateClientMFA),
  228. }
  229. twoFactorPath := []string{
  230. filepath.Join(templatesPath, templateClientDir, templateClientBaseLogin),
  231. filepath.Join(templatesPath, templateClientDir, templateClientTwoFactor),
  232. }
  233. twoFactorRecoveryPath := []string{
  234. filepath.Join(templatesPath, templateClientDir, templateClientBaseLogin),
  235. filepath.Join(templatesPath, templateClientDir, templateClientTwoFactorRecovery),
  236. }
  237. forgotPwdPaths := []string{
  238. filepath.Join(templatesPath, templateCommonDir, templateForgotPassword),
  239. }
  240. resetPwdPaths := []string{
  241. filepath.Join(templatesPath, templateCommonDir, templateResetPassword),
  242. }
  243. viewPDFPaths := []string{
  244. filepath.Join(templatesPath, templateClientDir, templateClientViewPDF),
  245. }
  246. shareFilesPath := []string{
  247. filepath.Join(templatesPath, templateClientDir, templateClientBase),
  248. filepath.Join(templatesPath, templateClientDir, templateShareFiles),
  249. }
  250. shareUploadPath := []string{
  251. filepath.Join(templatesPath, templateClientDir, templateClientBase),
  252. filepath.Join(templatesPath, templateClientDir, templateUploadToShare),
  253. }
  254. filesTmpl := util.LoadTemplate(nil, filesPaths...)
  255. profileTmpl := util.LoadTemplate(nil, profilePaths...)
  256. changePwdTmpl := util.LoadTemplate(nil, changePwdPaths...)
  257. loginTmpl := util.LoadTemplate(nil, loginPath...)
  258. messageTmpl := util.LoadTemplate(nil, messagePath...)
  259. mfaTmpl := util.LoadTemplate(nil, mfaPath...)
  260. twoFactorTmpl := util.LoadTemplate(nil, twoFactorPath...)
  261. twoFactorRecoveryTmpl := util.LoadTemplate(nil, twoFactorRecoveryPath...)
  262. editFileTmpl := util.LoadTemplate(nil, editFilePath...)
  263. sharesTmpl := util.LoadTemplate(nil, sharesPaths...)
  264. shareTmpl := util.LoadTemplate(nil, sharePaths...)
  265. forgotPwdTmpl := util.LoadTemplate(nil, forgotPwdPaths...)
  266. resetPwdTmpl := util.LoadTemplate(nil, resetPwdPaths...)
  267. viewPDFTmpl := util.LoadTemplate(nil, viewPDFPaths...)
  268. shareFilesTmpl := util.LoadTemplate(nil, shareFilesPath...)
  269. shareUploadTmpl := util.LoadTemplate(nil, shareUploadPath...)
  270. clientTemplates[templateClientFiles] = filesTmpl
  271. clientTemplates[templateClientProfile] = profileTmpl
  272. clientTemplates[templateClientChangePwd] = changePwdTmpl
  273. clientTemplates[templateClientLogin] = loginTmpl
  274. clientTemplates[templateClientMessage] = messageTmpl
  275. clientTemplates[templateClientMFA] = mfaTmpl
  276. clientTemplates[templateClientTwoFactor] = twoFactorTmpl
  277. clientTemplates[templateClientTwoFactorRecovery] = twoFactorRecoveryTmpl
  278. clientTemplates[templateClientEditFile] = editFileTmpl
  279. clientTemplates[templateClientShares] = sharesTmpl
  280. clientTemplates[templateClientShare] = shareTmpl
  281. clientTemplates[templateForgotPassword] = forgotPwdTmpl
  282. clientTemplates[templateResetPassword] = resetPwdTmpl
  283. clientTemplates[templateClientViewPDF] = viewPDFTmpl
  284. clientTemplates[templateShareFiles] = shareFilesTmpl
  285. clientTemplates[templateUploadToShare] = shareUploadTmpl
  286. }
  287. func (s *httpdServer) getBaseClientPageData(title, currentURL string, r *http.Request) baseClientPage {
  288. var csrfToken string
  289. if currentURL != "" {
  290. csrfToken = createCSRFToken(util.GetIPFromRemoteAddress(r.RemoteAddr))
  291. }
  292. v := version.Get()
  293. return baseClientPage{
  294. Title: title,
  295. CurrentURL: currentURL,
  296. FilesURL: webClientFilesPath,
  297. SharesURL: webClientSharesPath,
  298. ShareURL: webClientSharePath,
  299. ProfileURL: webClientProfilePath,
  300. ChangePwdURL: webChangeClientPwdPath,
  301. StaticURL: webStaticFilesPath,
  302. LogoutURL: webClientLogoutPath,
  303. MFAURL: webClientMFAPath,
  304. MFATitle: pageClient2FATitle,
  305. FilesTitle: pageClientFilesTitle,
  306. SharesTitle: pageClientSharesTitle,
  307. ProfileTitle: pageClientProfileTitle,
  308. Version: fmt.Sprintf("%v-%v", v.Version, v.CommitHash),
  309. CSRFToken: csrfToken,
  310. HasExternalLogin: isLoggedInWithOIDC(r),
  311. LoggedUser: getUserFromToken(r),
  312. ExtraCSS: s.binding.ExtraCSS,
  313. }
  314. }
  315. func (s *httpdServer) renderClientForgotPwdPage(w http.ResponseWriter, error, ip string) {
  316. data := forgotPwdPage{
  317. CurrentURL: webClientForgotPwdPath,
  318. Error: error,
  319. CSRFToken: createCSRFToken(ip),
  320. StaticURL: webStaticFilesPath,
  321. Title: pageClientForgotPwdTitle,
  322. ExtraCSS: s.binding.ExtraCSS,
  323. }
  324. renderClientTemplate(w, templateForgotPassword, data)
  325. }
  326. func (s *httpdServer) renderClientResetPwdPage(w http.ResponseWriter, error, ip string) {
  327. data := resetPwdPage{
  328. CurrentURL: webClientResetPwdPath,
  329. Error: error,
  330. CSRFToken: createCSRFToken(ip),
  331. StaticURL: webStaticFilesPath,
  332. Title: pageClientResetPwdTitle,
  333. ExtraCSS: s.binding.ExtraCSS,
  334. }
  335. renderClientTemplate(w, templateResetPassword, data)
  336. }
  337. func renderClientTemplate(w http.ResponseWriter, tmplName string, data interface{}) {
  338. err := clientTemplates[tmplName].ExecuteTemplate(w, tmplName, data)
  339. if err != nil {
  340. http.Error(w, err.Error(), http.StatusInternalServerError)
  341. }
  342. }
  343. func (s *httpdServer) renderClientMessagePage(w http.ResponseWriter, r *http.Request, title, body string, statusCode int, err error, message string) {
  344. var errorString string
  345. if body != "" {
  346. errorString = body + " "
  347. }
  348. if err != nil {
  349. errorString += err.Error()
  350. }
  351. data := clientMessagePage{
  352. baseClientPage: s.getBaseClientPageData(title, "", r),
  353. Error: errorString,
  354. Success: message,
  355. }
  356. w.WriteHeader(statusCode)
  357. renderClientTemplate(w, templateClientMessage, data)
  358. }
  359. func (s *httpdServer) renderClientInternalServerErrorPage(w http.ResponseWriter, r *http.Request, err error) {
  360. s.renderClientMessagePage(w, r, page500Title, page500Body, http.StatusInternalServerError, err, "")
  361. }
  362. func (s *httpdServer) renderClientBadRequestPage(w http.ResponseWriter, r *http.Request, err error) {
  363. s.renderClientMessagePage(w, r, page400Title, "", http.StatusBadRequest, err, "")
  364. }
  365. func (s *httpdServer) renderClientForbiddenPage(w http.ResponseWriter, r *http.Request, body string) {
  366. s.renderClientMessagePage(w, r, page403Title, "", http.StatusForbidden, nil, body)
  367. }
  368. func (s *httpdServer) renderClientNotFoundPage(w http.ResponseWriter, r *http.Request, err error) {
  369. s.renderClientMessagePage(w, r, page404Title, page404Body, http.StatusNotFound, err, "")
  370. }
  371. func (s *httpdServer) renderClientTwoFactorPage(w http.ResponseWriter, error, ip string) {
  372. data := twoFactorPage{
  373. CurrentURL: webClientTwoFactorPath,
  374. Version: version.Get().Version,
  375. Error: error,
  376. CSRFToken: createCSRFToken(ip),
  377. StaticURL: webStaticFilesPath,
  378. RecoveryURL: webClientTwoFactorRecoveryPath,
  379. ExtraCSS: s.binding.ExtraCSS,
  380. }
  381. renderClientTemplate(w, templateTwoFactor, data)
  382. }
  383. func (s *httpdServer) renderClientTwoFactorRecoveryPage(w http.ResponseWriter, error, ip string) {
  384. data := twoFactorPage{
  385. CurrentURL: webClientTwoFactorRecoveryPath,
  386. Version: version.Get().Version,
  387. Error: error,
  388. CSRFToken: createCSRFToken(ip),
  389. StaticURL: webStaticFilesPath,
  390. ExtraCSS: s.binding.ExtraCSS,
  391. }
  392. renderClientTemplate(w, templateTwoFactorRecovery, data)
  393. }
  394. func (s *httpdServer) renderClientMFAPage(w http.ResponseWriter, r *http.Request) {
  395. data := clientMFAPage{
  396. baseClientPage: s.getBaseClientPageData(pageMFATitle, webClientMFAPath, r),
  397. TOTPConfigs: mfa.GetAvailableTOTPConfigNames(),
  398. GenerateTOTPURL: webClientTOTPGeneratePath,
  399. ValidateTOTPURL: webClientTOTPValidatePath,
  400. SaveTOTPURL: webClientTOTPSavePath,
  401. RecCodesURL: webClientRecoveryCodesPath,
  402. Protocols: dataprovider.MFAProtocols,
  403. }
  404. user, err := dataprovider.UserExists(data.LoggedUser.Username)
  405. if err != nil {
  406. s.renderInternalServerErrorPage(w, r, err)
  407. return
  408. }
  409. data.TOTPConfig = user.Filters.TOTPConfig
  410. renderClientTemplate(w, templateClientMFA, data)
  411. }
  412. func (s *httpdServer) renderEditFilePage(w http.ResponseWriter, r *http.Request, fileName, fileData string, readOnly bool) {
  413. data := editFilePage{
  414. baseClientPage: s.getBaseClientPageData(pageClientEditFileTitle, webClientEditFilePath, r),
  415. Path: fileName,
  416. Name: path.Base(fileName),
  417. CurrentDir: path.Dir(fileName),
  418. FileURL: webClientFilePath,
  419. ReadOnly: readOnly,
  420. Data: fileData,
  421. }
  422. renderClientTemplate(w, templateClientEditFile, data)
  423. }
  424. func (s *httpdServer) renderAddUpdateSharePage(w http.ResponseWriter, r *http.Request, share *dataprovider.Share,
  425. error string, isAdd bool) {
  426. currentURL := webClientSharePath
  427. title := "Add a new share"
  428. if !isAdd {
  429. currentURL = fmt.Sprintf("%v/%v", webClientSharePath, url.PathEscape(share.ShareID))
  430. title = "Update share"
  431. }
  432. data := clientSharePage{
  433. baseClientPage: s.getBaseClientPageData(title, currentURL, r),
  434. Share: share,
  435. Error: error,
  436. IsAdd: isAdd,
  437. }
  438. renderClientTemplate(w, templateClientShare, data)
  439. }
  440. func getDirMapping(dirName, baseWebPath string) []dirMapping {
  441. paths := []dirMapping{}
  442. if dirName != "/" {
  443. paths = append(paths, dirMapping{
  444. DirName: path.Base(dirName),
  445. Href: "",
  446. })
  447. for {
  448. dirName = path.Dir(dirName)
  449. if dirName == "/" || dirName == "." {
  450. break
  451. }
  452. paths = append([]dirMapping{{
  453. DirName: path.Base(dirName),
  454. Href: getFileObjectURL("/", dirName, baseWebPath)},
  455. }, paths...)
  456. }
  457. }
  458. return paths
  459. }
  460. func (s *httpdServer) renderSharedFilesPage(w http.ResponseWriter, r *http.Request, dirName, error string,
  461. share dataprovider.Share,
  462. ) {
  463. currentURL := path.Join(webClientPubSharesPath, share.ShareID, "browse")
  464. data := shareFilesPage{
  465. baseClientPage: s.getBaseClientPageData(pageExtShareTitle, currentURL, r),
  466. CurrentDir: url.QueryEscape(dirName),
  467. DirsURL: path.Join(webClientPubSharesPath, share.ShareID, "dirs"),
  468. FilesURL: currentURL,
  469. DownloadURL: path.Join(webClientPubSharesPath, share.ShareID),
  470. Error: error,
  471. Paths: getDirMapping(dirName, currentURL),
  472. }
  473. renderClientTemplate(w, templateShareFiles, data)
  474. }
  475. func (s *httpdServer) renderUploadToSharePage(w http.ResponseWriter, r *http.Request, share dataprovider.Share) {
  476. currentURL := path.Join(webClientPubSharesPath, share.ShareID, "upload")
  477. data := shareUploadPage{
  478. baseClientPage: s.getBaseClientPageData(pageUploadToShareTitle, currentURL, r),
  479. Share: &share,
  480. UploadBasePath: path.Join(webClientPubSharesPath, share.ShareID),
  481. }
  482. renderClientTemplate(w, templateUploadToShare, data)
  483. }
  484. func (s *httpdServer) renderFilesPage(w http.ResponseWriter, r *http.Request, dirName, error string, user dataprovider.User,
  485. hasIntegrations bool,
  486. ) {
  487. data := filesPage{
  488. baseClientPage: s.getBaseClientPageData(pageClientFilesTitle, webClientFilesPath, r),
  489. Error: error,
  490. CurrentDir: url.QueryEscape(dirName),
  491. DownloadURL: webClientDownloadZipPath,
  492. ViewPDFURL: webClientViewPDFPath,
  493. DirsURL: webClientDirsPath,
  494. FileURL: webClientFilePath,
  495. CanAddFiles: user.CanAddFilesFromWeb(dirName),
  496. CanCreateDirs: user.CanAddDirsFromWeb(dirName),
  497. CanRename: user.CanRenameFromWeb(dirName, dirName),
  498. CanDelete: user.CanDeleteFromWeb(dirName),
  499. CanDownload: user.HasPerm(dataprovider.PermDownload, dirName),
  500. CanShare: user.CanManageShares(),
  501. HasIntegrations: hasIntegrations,
  502. Paths: getDirMapping(dirName, webClientFilesPath),
  503. }
  504. renderClientTemplate(w, templateClientFiles, data)
  505. }
  506. func (s *httpdServer) renderClientProfilePage(w http.ResponseWriter, r *http.Request, error string) {
  507. data := clientProfilePage{
  508. baseClientPage: s.getBaseClientPageData(pageClientProfileTitle, webClientProfilePath, r),
  509. Error: error,
  510. }
  511. user, err := dataprovider.UserExists(data.LoggedUser.Username)
  512. if err != nil {
  513. s.renderClientInternalServerErrorPage(w, r, err)
  514. return
  515. }
  516. data.PublicKeys = user.PublicKeys
  517. data.AllowAPIKeyAuth = user.Filters.AllowAPIKeyAuth
  518. data.Email = user.Email
  519. data.Description = user.Description
  520. data.CanSubmit = user.CanChangeAPIKeyAuth() || user.CanManagePublicKeys() || user.CanChangeInfo()
  521. renderClientTemplate(w, templateClientProfile, data)
  522. }
  523. func (s *httpdServer) renderClientChangePasswordPage(w http.ResponseWriter, r *http.Request, error string) {
  524. data := changeClientPasswordPage{
  525. baseClientPage: s.getBaseClientPageData(pageClientChangePwdTitle, webChangeClientPwdPath, r),
  526. Error: error,
  527. }
  528. renderClientTemplate(w, templateClientChangePwd, data)
  529. }
  530. func (s *httpdServer) handleWebClientDownloadZip(w http.ResponseWriter, r *http.Request) {
  531. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  532. claims, err := getTokenClaims(r)
  533. if err != nil || claims.Username == "" {
  534. s.renderClientMessagePage(w, r, "Invalid token claims", "", http.StatusForbidden, nil, "")
  535. return
  536. }
  537. user, err := dataprovider.UserExists(claims.Username)
  538. if err != nil {
  539. s.renderClientMessagePage(w, r, "Unable to retrieve your user", "", getRespStatus(err), nil, "")
  540. return
  541. }
  542. connID := xid.New().String()
  543. protocol := getProtocolFromRequest(r)
  544. connectionID := fmt.Sprintf("%v_%v", protocol, connID)
  545. if err := checkHTTPClientUser(&user, r, connectionID); err != nil {
  546. s.renderClientForbiddenPage(w, r, err.Error())
  547. return
  548. }
  549. connection := &Connection{
  550. BaseConnection: common.NewBaseConnection(connID, protocol, util.GetHTTPLocalAddress(r),
  551. r.RemoteAddr, user),
  552. request: r,
  553. }
  554. common.Connections.Add(connection)
  555. defer common.Connections.Remove(connection.GetID())
  556. name := connection.User.GetCleanedPath(r.URL.Query().Get("path"))
  557. files := r.URL.Query().Get("files")
  558. var filesList []string
  559. err = json.Unmarshal([]byte(files), &filesList)
  560. if err != nil {
  561. s.renderClientMessagePage(w, r, "Unable to get files list", "", http.StatusInternalServerError, err, "")
  562. return
  563. }
  564. w.Header().Set("Content-Disposition", "attachment; filename=\"sftpgo-download.zip\"")
  565. renderCompressedFiles(w, connection, name, filesList, nil)
  566. }
  567. func (s *httpdServer) handleShareGetDirContents(w http.ResponseWriter, r *http.Request) {
  568. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  569. share, connection, err := s.checkPublicShare(w, r, dataprovider.ShareScopeRead, true)
  570. if err != nil {
  571. return
  572. }
  573. if err := validateBrowsableShare(share, connection); err != nil {
  574. s.renderClientMessagePage(w, r, "Unable to validate share", "", getRespStatus(err), err, "")
  575. return
  576. }
  577. name, err := getBrowsableSharedPath(share, r)
  578. if err != nil {
  579. s.renderClientMessagePage(w, r, "Invalid share path", "", getRespStatus(err), err, "")
  580. return
  581. }
  582. common.Connections.Add(connection)
  583. defer common.Connections.Remove(connection.GetID())
  584. contents, err := connection.ReadDir(name)
  585. if err != nil {
  586. sendAPIResponse(w, r, err, "Unable to get directory contents", getMappedStatusCode(err))
  587. return
  588. }
  589. results := make([]map[string]string, 0, len(contents))
  590. for _, info := range contents {
  591. if !info.Mode().IsDir() && !info.Mode().IsRegular() {
  592. continue
  593. }
  594. res := make(map[string]string)
  595. if info.IsDir() {
  596. res["type"] = "1"
  597. res["size"] = ""
  598. } else {
  599. res["type"] = "2"
  600. res["size"] = util.ByteCountIEC(info.Size())
  601. }
  602. res["name"] = info.Name()
  603. res["url"] = getFileObjectURL(share.GetRelativePath(name), info.Name(),
  604. path.Join(webClientPubSharesPath, share.ShareID, "browse"))
  605. res["last_modified"] = getFileObjectModTime(info.ModTime())
  606. results = append(results, res)
  607. }
  608. render.JSON(w, r, results)
  609. }
  610. func (s *httpdServer) handleClientUploadToShare(w http.ResponseWriter, r *http.Request) {
  611. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  612. share, _, err := s.checkPublicShare(w, r, dataprovider.ShareScopeWrite, true)
  613. if err != nil {
  614. return
  615. }
  616. s.renderUploadToSharePage(w, r, share)
  617. }
  618. func (s *httpdServer) handleShareGetFiles(w http.ResponseWriter, r *http.Request) {
  619. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  620. share, connection, err := s.checkPublicShare(w, r, dataprovider.ShareScopeRead, true)
  621. if err != nil {
  622. return
  623. }
  624. if err := validateBrowsableShare(share, connection); err != nil {
  625. s.renderClientMessagePage(w, r, "Unable to validate share", "", getRespStatus(err), err, "")
  626. return
  627. }
  628. name, err := getBrowsableSharedPath(share, r)
  629. if err != nil {
  630. s.renderClientMessagePage(w, r, "Invalid share path", "", getRespStatus(err), err, "")
  631. return
  632. }
  633. common.Connections.Add(connection)
  634. defer common.Connections.Remove(connection.GetID())
  635. var info os.FileInfo
  636. if name == "/" {
  637. info = vfs.NewFileInfo(name, true, 0, time.Now(), false)
  638. } else {
  639. info, err = connection.Stat(name, 1)
  640. }
  641. if err != nil {
  642. s.renderSharedFilesPage(w, r, path.Dir(share.GetRelativePath(name)), err.Error(), share)
  643. return
  644. }
  645. if info.IsDir() {
  646. s.renderSharedFilesPage(w, r, share.GetRelativePath(name), "", share)
  647. return
  648. }
  649. inline := r.URL.Query().Get("inline") != ""
  650. dataprovider.UpdateShareLastUse(&share, 1) //nolint:errcheck
  651. if status, err := downloadFile(w, r, connection, name, info, inline, &share); err != nil {
  652. dataprovider.UpdateShareLastUse(&share, -1) //nolint:errcheck
  653. if status > 0 {
  654. s.renderSharedFilesPage(w, r, path.Dir(share.GetRelativePath(name)), err.Error(), share)
  655. }
  656. }
  657. }
  658. func (s *httpdServer) handleClientGetDirContents(w http.ResponseWriter, r *http.Request) {
  659. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  660. claims, err := getTokenClaims(r)
  661. if err != nil || claims.Username == "" {
  662. sendAPIResponse(w, r, nil, "invalid token claims", http.StatusForbidden)
  663. return
  664. }
  665. user, err := dataprovider.UserExists(claims.Username)
  666. if err != nil {
  667. sendAPIResponse(w, r, nil, "Unable to retrieve your user", getRespStatus(err))
  668. return
  669. }
  670. connID := xid.New().String()
  671. protocol := getProtocolFromRequest(r)
  672. connectionID := fmt.Sprintf("%v_%v", protocol, connID)
  673. if err := checkHTTPClientUser(&user, r, connectionID); err != nil {
  674. sendAPIResponse(w, r, err, http.StatusText(http.StatusForbidden), http.StatusForbidden)
  675. return
  676. }
  677. connection := &Connection{
  678. BaseConnection: common.NewBaseConnection(connID, protocol, util.GetHTTPLocalAddress(r),
  679. r.RemoteAddr, user),
  680. request: r,
  681. }
  682. common.Connections.Add(connection)
  683. defer common.Connections.Remove(connection.GetID())
  684. name := connection.User.GetCleanedPath(r.URL.Query().Get("path"))
  685. contents, err := connection.ReadDir(name)
  686. if err != nil {
  687. sendAPIResponse(w, r, err, "Unable to get directory contents", getMappedStatusCode(err))
  688. return
  689. }
  690. results := make([]map[string]string, 0, len(contents))
  691. for _, info := range contents {
  692. res := make(map[string]string)
  693. res["url"] = getFileObjectURL(name, info.Name(), webClientFilesPath)
  694. if info.IsDir() {
  695. res["type"] = "1"
  696. res["size"] = ""
  697. } else {
  698. res["type"] = "2"
  699. if info.Mode()&os.ModeSymlink != 0 {
  700. res["size"] = ""
  701. } else {
  702. res["size"] = util.ByteCountIEC(info.Size())
  703. if info.Size() < httpdMaxEditFileSize {
  704. res["edit_url"] = strings.Replace(res["url"], webClientFilesPath, webClientEditFilePath, 1)
  705. }
  706. if len(s.binding.WebClientIntegrations) > 0 {
  707. extension := path.Ext(info.Name())
  708. for idx := range s.binding.WebClientIntegrations {
  709. if util.IsStringInSlice(extension, s.binding.WebClientIntegrations[idx].FileExtensions) {
  710. res["ext_url"] = s.binding.WebClientIntegrations[idx].URL
  711. res["ext_link"] = fmt.Sprintf("%v?path=%v&_=%v", webClientFilePath,
  712. url.QueryEscape(path.Join(name, info.Name())), time.Now().UTC().Unix())
  713. break
  714. }
  715. }
  716. }
  717. }
  718. }
  719. res["meta"] = fmt.Sprintf("%v_%v", res["type"], info.Name())
  720. res["name"] = info.Name()
  721. res["last_modified"] = getFileObjectModTime(info.ModTime())
  722. results = append(results, res)
  723. }
  724. render.JSON(w, r, results)
  725. }
  726. func (s *httpdServer) handleClientGetFiles(w http.ResponseWriter, r *http.Request) {
  727. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  728. claims, err := getTokenClaims(r)
  729. if err != nil || claims.Username == "" {
  730. s.renderClientForbiddenPage(w, r, "Invalid token claims")
  731. return
  732. }
  733. user, err := dataprovider.UserExists(claims.Username)
  734. if err != nil {
  735. s.renderClientMessagePage(w, r, "Unable to retrieve your user", "", getRespStatus(err), nil, "")
  736. return
  737. }
  738. connID := xid.New().String()
  739. protocol := getProtocolFromRequest(r)
  740. connectionID := fmt.Sprintf("%v_%v", protocol, connID)
  741. if err := checkHTTPClientUser(&user, r, connectionID); err != nil {
  742. s.renderClientForbiddenPage(w, r, err.Error())
  743. return
  744. }
  745. connection := &Connection{
  746. BaseConnection: common.NewBaseConnection(connID, protocol, util.GetHTTPLocalAddress(r),
  747. r.RemoteAddr, user),
  748. request: r,
  749. }
  750. common.Connections.Add(connection)
  751. defer common.Connections.Remove(connection.GetID())
  752. name := connection.User.GetCleanedPath(r.URL.Query().Get("path"))
  753. var info os.FileInfo
  754. if name == "/" {
  755. info = vfs.NewFileInfo(name, true, 0, time.Now(), false)
  756. } else {
  757. info, err = connection.Stat(name, 0)
  758. }
  759. if err != nil {
  760. s.renderFilesPage(w, r, path.Dir(name), fmt.Sprintf("unable to stat file %#v: %v", name, err),
  761. user, len(s.binding.WebClientIntegrations) > 0)
  762. return
  763. }
  764. if info.IsDir() {
  765. s.renderFilesPage(w, r, name, "", user, len(s.binding.WebClientIntegrations) > 0)
  766. return
  767. }
  768. inline := r.URL.Query().Get("inline") != ""
  769. if status, err := downloadFile(w, r, connection, name, info, inline, nil); err != nil && status != 0 {
  770. if status > 0 {
  771. if status == http.StatusRequestedRangeNotSatisfiable {
  772. s.renderClientMessagePage(w, r, http.StatusText(status), "", status, err, "")
  773. return
  774. }
  775. s.renderFilesPage(w, r, path.Dir(name), err.Error(), user, len(s.binding.WebClientIntegrations) > 0)
  776. }
  777. }
  778. }
  779. func (s *httpdServer) handleClientEditFile(w http.ResponseWriter, r *http.Request) {
  780. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  781. claims, err := getTokenClaims(r)
  782. if err != nil || claims.Username == "" {
  783. s.renderClientForbiddenPage(w, r, "Invalid token claims")
  784. return
  785. }
  786. user, err := dataprovider.UserExists(claims.Username)
  787. if err != nil {
  788. s.renderClientMessagePage(w, r, "Unable to retrieve your user", "", getRespStatus(err), nil, "")
  789. return
  790. }
  791. connID := xid.New().String()
  792. protocol := getProtocolFromRequest(r)
  793. connectionID := fmt.Sprintf("%v_%v", protocol, connID)
  794. if err := checkHTTPClientUser(&user, r, connectionID); err != nil {
  795. s.renderClientForbiddenPage(w, r, err.Error())
  796. return
  797. }
  798. connection := &Connection{
  799. BaseConnection: common.NewBaseConnection(connID, protocol, util.GetHTTPLocalAddress(r),
  800. r.RemoteAddr, user),
  801. request: r,
  802. }
  803. common.Connections.Add(connection)
  804. defer common.Connections.Remove(connection.GetID())
  805. name := connection.User.GetCleanedPath(r.URL.Query().Get("path"))
  806. info, err := connection.Stat(name, 0)
  807. if err != nil {
  808. s.renderClientMessagePage(w, r, fmt.Sprintf("Unable to stat file %#v", name), "",
  809. getRespStatus(err), nil, "")
  810. return
  811. }
  812. if info.IsDir() {
  813. s.renderClientMessagePage(w, r, fmt.Sprintf("The path %#v does not point to a file", name), "",
  814. http.StatusBadRequest, nil, "")
  815. return
  816. }
  817. if info.Size() > httpdMaxEditFileSize {
  818. s.renderClientMessagePage(w, r, fmt.Sprintf("The file size %v for %#v exceeds the maximum allowed size",
  819. util.ByteCountIEC(info.Size()), name), "", http.StatusBadRequest, nil, "")
  820. return
  821. }
  822. reader, err := connection.getFileReader(name, 0, r.Method)
  823. if err != nil {
  824. s.renderClientMessagePage(w, r, fmt.Sprintf("Unable to get a reader for the file %#v", name), "",
  825. getRespStatus(err), nil, "")
  826. return
  827. }
  828. defer reader.Close()
  829. var b bytes.Buffer
  830. _, err = io.Copy(&b, reader)
  831. if err != nil {
  832. s.renderClientMessagePage(w, r, fmt.Sprintf("Unable to read the file %#v", name), "", http.StatusInternalServerError,
  833. nil, "")
  834. return
  835. }
  836. s.renderEditFilePage(w, r, name, b.String(), util.IsStringInSlice(sdk.WebClientWriteDisabled, user.Filters.WebClient))
  837. }
  838. func (s *httpdServer) handleClientAddShareGet(w http.ResponseWriter, r *http.Request) {
  839. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  840. share := &dataprovider.Share{Scope: dataprovider.ShareScopeRead}
  841. dirName := "/"
  842. if _, ok := r.URL.Query()["path"]; ok {
  843. dirName = util.CleanPath(r.URL.Query().Get("path"))
  844. }
  845. if _, ok := r.URL.Query()["files"]; ok {
  846. files := r.URL.Query().Get("files")
  847. var filesList []string
  848. err := json.Unmarshal([]byte(files), &filesList)
  849. if err != nil {
  850. s.renderClientMessagePage(w, r, "Invalid share list", "", http.StatusBadRequest, err, "")
  851. return
  852. }
  853. for _, f := range filesList {
  854. if f != "" {
  855. share.Paths = append(share.Paths, path.Join(dirName, f))
  856. }
  857. }
  858. }
  859. s.renderAddUpdateSharePage(w, r, share, "", true)
  860. }
  861. func (s *httpdServer) handleClientUpdateShareGet(w http.ResponseWriter, r *http.Request) {
  862. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  863. claims, err := getTokenClaims(r)
  864. if err != nil || claims.Username == "" {
  865. s.renderClientForbiddenPage(w, r, "Invalid token claims")
  866. return
  867. }
  868. shareID := getURLParam(r, "id")
  869. share, err := dataprovider.ShareExists(shareID, claims.Username)
  870. if err == nil {
  871. share.HideConfidentialData()
  872. s.renderAddUpdateSharePage(w, r, &share, "", false)
  873. } else if _, ok := err.(*util.RecordNotFoundError); ok {
  874. s.renderClientNotFoundPage(w, r, err)
  875. } else {
  876. s.renderClientInternalServerErrorPage(w, r, err)
  877. }
  878. }
  879. func (s *httpdServer) handleClientAddSharePost(w http.ResponseWriter, r *http.Request) {
  880. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  881. claims, err := getTokenClaims(r)
  882. if err != nil || claims.Username == "" {
  883. s.renderClientForbiddenPage(w, r, "Invalid token claims")
  884. return
  885. }
  886. share, err := getShareFromPostFields(r)
  887. if err != nil {
  888. s.renderAddUpdateSharePage(w, r, share, err.Error(), true)
  889. return
  890. }
  891. ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
  892. if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil {
  893. s.renderClientForbiddenPage(w, r, err.Error())
  894. return
  895. }
  896. share.ID = 0
  897. share.ShareID = util.GenerateUniqueID()
  898. share.LastUseAt = 0
  899. share.Username = claims.Username
  900. if share.Password == "" {
  901. if util.IsStringInSlice(sdk.WebClientShareNoPasswordDisabled, claims.Permissions) {
  902. s.renderClientForbiddenPage(w, r, "You are not authorized to share files/folders without a password")
  903. return
  904. }
  905. }
  906. err = dataprovider.AddShare(share, claims.Username, ipAddr)
  907. if err == nil {
  908. http.Redirect(w, r, webClientSharesPath, http.StatusSeeOther)
  909. } else {
  910. s.renderAddUpdateSharePage(w, r, share, err.Error(), true)
  911. }
  912. }
  913. func (s *httpdServer) handleClientUpdateSharePost(w http.ResponseWriter, r *http.Request) {
  914. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  915. claims, err := getTokenClaims(r)
  916. if err != nil || claims.Username == "" {
  917. s.renderClientForbiddenPage(w, r, "Invalid token claims")
  918. return
  919. }
  920. shareID := getURLParam(r, "id")
  921. share, err := dataprovider.ShareExists(shareID, claims.Username)
  922. if _, ok := err.(*util.RecordNotFoundError); ok {
  923. s.renderClientNotFoundPage(w, r, err)
  924. return
  925. } else if err != nil {
  926. s.renderClientInternalServerErrorPage(w, r, err)
  927. return
  928. }
  929. updatedShare, err := getShareFromPostFields(r)
  930. if err != nil {
  931. s.renderAddUpdateSharePage(w, r, updatedShare, err.Error(), false)
  932. return
  933. }
  934. ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
  935. if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil {
  936. s.renderClientForbiddenPage(w, r, err.Error())
  937. return
  938. }
  939. updatedShare.ShareID = shareID
  940. updatedShare.Username = claims.Username
  941. if updatedShare.Password == redactedSecret {
  942. updatedShare.Password = share.Password
  943. }
  944. if updatedShare.Password == "" {
  945. if util.IsStringInSlice(sdk.WebClientShareNoPasswordDisabled, claims.Permissions) {
  946. s.renderClientForbiddenPage(w, r, "You are not authorized to share files/folders without a password")
  947. return
  948. }
  949. }
  950. err = dataprovider.UpdateShare(updatedShare, claims.Username, ipAddr)
  951. if err == nil {
  952. http.Redirect(w, r, webClientSharesPath, http.StatusSeeOther)
  953. } else {
  954. s.renderAddUpdateSharePage(w, r, updatedShare, err.Error(), false)
  955. }
  956. }
  957. func (s *httpdServer) handleClientGetShares(w http.ResponseWriter, r *http.Request) {
  958. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  959. claims, err := getTokenClaims(r)
  960. if err != nil || claims.Username == "" {
  961. s.renderClientForbiddenPage(w, r, "Invalid token claims")
  962. return
  963. }
  964. limit := defaultQueryLimit
  965. if _, ok := r.URL.Query()["qlimit"]; ok {
  966. var err error
  967. limit, err = strconv.Atoi(r.URL.Query().Get("qlimit"))
  968. if err != nil {
  969. limit = defaultQueryLimit
  970. }
  971. }
  972. shares := make([]dataprovider.Share, 0, limit)
  973. for {
  974. sh, err := dataprovider.GetShares(limit, len(shares), dataprovider.OrderASC, claims.Username)
  975. if err != nil {
  976. s.renderInternalServerErrorPage(w, r, err)
  977. return
  978. }
  979. shares = append(shares, sh...)
  980. if len(sh) < limit {
  981. break
  982. }
  983. }
  984. data := clientSharesPage{
  985. baseClientPage: s.getBaseClientPageData(pageClientSharesTitle, webClientSharesPath, r),
  986. Shares: shares,
  987. BasePublicSharesURL: webClientPubSharesPath,
  988. }
  989. renderClientTemplate(w, templateClientShares, data)
  990. }
  991. func (s *httpdServer) handleClientGetProfile(w http.ResponseWriter, r *http.Request) {
  992. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  993. s.renderClientProfilePage(w, r, "")
  994. }
  995. func (s *httpdServer) handleWebClientChangePwd(w http.ResponseWriter, r *http.Request) {
  996. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  997. s.renderClientChangePasswordPage(w, r, "")
  998. }
  999. func (s *httpdServer) handleWebClientProfilePost(w http.ResponseWriter, r *http.Request) {
  1000. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1001. err := r.ParseForm()
  1002. if err != nil {
  1003. s.renderClientProfilePage(w, r, err.Error())
  1004. return
  1005. }
  1006. ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
  1007. if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil {
  1008. s.renderClientForbiddenPage(w, r, err.Error())
  1009. return
  1010. }
  1011. claims, err := getTokenClaims(r)
  1012. if err != nil || claims.Username == "" {
  1013. s.renderClientForbiddenPage(w, r, "Invalid token claims")
  1014. return
  1015. }
  1016. user, err := dataprovider.UserExists(claims.Username)
  1017. if err != nil {
  1018. s.renderClientProfilePage(w, r, err.Error())
  1019. return
  1020. }
  1021. if !user.CanManagePublicKeys() && !user.CanChangeAPIKeyAuth() && !user.CanChangeInfo() {
  1022. s.renderClientForbiddenPage(w, r, "You are not allowed to change anything")
  1023. return
  1024. }
  1025. if user.CanManagePublicKeys() {
  1026. user.PublicKeys = r.Form["public_keys"]
  1027. }
  1028. if user.CanChangeAPIKeyAuth() {
  1029. user.Filters.AllowAPIKeyAuth = len(r.Form.Get("allow_api_key_auth")) > 0
  1030. }
  1031. if user.CanChangeInfo() {
  1032. user.Email = r.Form.Get("email")
  1033. user.Description = r.Form.Get("description")
  1034. }
  1035. err = dataprovider.UpdateUser(&user, dataprovider.ActionExecutorSelf, ipAddr)
  1036. if err != nil {
  1037. s.renderClientProfilePage(w, r, err.Error())
  1038. return
  1039. }
  1040. s.renderClientMessagePage(w, r, "Profile updated", "", http.StatusOK, nil,
  1041. "Your profile has been successfully updated")
  1042. }
  1043. func (s *httpdServer) handleWebClientMFA(w http.ResponseWriter, r *http.Request) {
  1044. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1045. s.renderClientMFAPage(w, r)
  1046. }
  1047. func (s *httpdServer) handleWebClientTwoFactor(w http.ResponseWriter, r *http.Request) {
  1048. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1049. s.renderClientTwoFactorPage(w, "", util.GetIPFromRemoteAddress(r.RemoteAddr))
  1050. }
  1051. func (s *httpdServer) handleWebClientTwoFactorRecovery(w http.ResponseWriter, r *http.Request) {
  1052. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1053. s.renderClientTwoFactorRecoveryPage(w, "", util.GetIPFromRemoteAddress(r.RemoteAddr))
  1054. }
  1055. func getShareFromPostFields(r *http.Request) (*dataprovider.Share, error) {
  1056. share := &dataprovider.Share{}
  1057. if err := r.ParseForm(); err != nil {
  1058. return share, err
  1059. }
  1060. share.Name = r.Form.Get("name")
  1061. share.Description = r.Form.Get("description")
  1062. share.Paths = r.Form["paths"]
  1063. share.Password = r.Form.Get("password")
  1064. share.AllowFrom = getSliceFromDelimitedValues(r.Form.Get("allowed_ip"), ",")
  1065. scope, err := strconv.Atoi(r.Form.Get("scope"))
  1066. if err != nil {
  1067. return share, err
  1068. }
  1069. share.Scope = dataprovider.ShareScope(scope)
  1070. maxTokens, err := strconv.Atoi(r.Form.Get("max_tokens"))
  1071. if err != nil {
  1072. return share, err
  1073. }
  1074. share.MaxTokens = maxTokens
  1075. expirationDateMillis := int64(0)
  1076. expirationDateString := r.Form.Get("expiration_date")
  1077. if strings.TrimSpace(expirationDateString) != "" {
  1078. expirationDate, err := time.Parse(webDateTimeFormat, expirationDateString)
  1079. if err != nil {
  1080. return share, err
  1081. }
  1082. expirationDateMillis = util.GetTimeAsMsSinceEpoch(expirationDate)
  1083. }
  1084. share.ExpiresAt = expirationDateMillis
  1085. return share, nil
  1086. }
  1087. func (s *httpdServer) handleWebClientForgotPwd(w http.ResponseWriter, r *http.Request) {
  1088. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1089. if !smtp.IsEnabled() {
  1090. s.renderClientNotFoundPage(w, r, errors.New("this page does not exist"))
  1091. return
  1092. }
  1093. s.renderClientForgotPwdPage(w, "", util.GetIPFromRemoteAddress(r.RemoteAddr))
  1094. }
  1095. func (s *httpdServer) handleWebClientForgotPwdPost(w http.ResponseWriter, r *http.Request) {
  1096. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  1097. ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
  1098. err := r.ParseForm()
  1099. if err != nil {
  1100. s.renderClientForgotPwdPage(w, err.Error(), ipAddr)
  1101. return
  1102. }
  1103. if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil {
  1104. s.renderClientForbiddenPage(w, r, err.Error())
  1105. return
  1106. }
  1107. username := r.Form.Get("username")
  1108. err = handleForgotPassword(r, username, false)
  1109. if err != nil {
  1110. if e, ok := err.(*util.ValidationError); ok {
  1111. s.renderClientForgotPwdPage(w, e.GetErrorString(), ipAddr)
  1112. return
  1113. }
  1114. s.renderClientForgotPwdPage(w, err.Error(), ipAddr)
  1115. return
  1116. }
  1117. http.Redirect(w, r, webClientResetPwdPath, http.StatusFound)
  1118. }
  1119. func (s *httpdServer) handleWebClientPasswordReset(w http.ResponseWriter, r *http.Request) {
  1120. r.Body = http.MaxBytesReader(w, r.Body, maxLoginBodySize)
  1121. if !smtp.IsEnabled() {
  1122. s.renderClientNotFoundPage(w, r, errors.New("this page does not exist"))
  1123. return
  1124. }
  1125. s.renderClientResetPwdPage(w, "", util.GetIPFromRemoteAddress(r.RemoteAddr))
  1126. }
  1127. func (s *httpdServer) handleClientViewPDF(w http.ResponseWriter, r *http.Request) {
  1128. r.Body = http.MaxBytesReader(w, r.Body, maxLoginBodySize)
  1129. name := r.URL.Query().Get("path")
  1130. if name == "" {
  1131. s.renderClientBadRequestPage(w, r, errors.New("no file specified"))
  1132. return
  1133. }
  1134. name = util.CleanPath(name)
  1135. data := viewPDFPage{
  1136. Title: path.Base(name),
  1137. URL: fmt.Sprintf("%v?path=%v&inline=1", webClientFilesPath, url.QueryEscape(name)),
  1138. StaticURL: webStaticFilesPath,
  1139. ExtraCSS: s.binding.ExtraCSS,
  1140. }
  1141. renderClientTemplate(w, templateClientViewPDF, data)
  1142. }