httpd.go 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112
  1. // Copyright (C) 2019-2022 Nicola Murino
  2. //
  3. // This program is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU Affero General Public License as published
  5. // by the Free Software Foundation, version 3.
  6. //
  7. // This program is distributed in the hope that it will be useful,
  8. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. // GNU Affero General Public License for more details.
  11. //
  12. // You should have received a copy of the GNU Affero General Public License
  13. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  14. // Package httpd implements REST API and Web interface for SFTPGo.
  15. // The OpenAPI 3 schema for the exposed API can be found inside the source tree:
  16. // https://github.com/drakkan/sftpgo/blob/main/openapi/openapi.yaml
  17. package httpd
  18. import (
  19. "crypto/sha256"
  20. "errors"
  21. "fmt"
  22. "net"
  23. "net/http"
  24. "path"
  25. "path/filepath"
  26. "runtime"
  27. "strings"
  28. "sync"
  29. "time"
  30. "github.com/go-chi/chi/v5"
  31. "github.com/go-chi/jwtauth/v5"
  32. "github.com/lestrrat-go/jwx/v2/jwa"
  33. "github.com/drakkan/sftpgo/v2/internal/common"
  34. "github.com/drakkan/sftpgo/v2/internal/dataprovider"
  35. "github.com/drakkan/sftpgo/v2/internal/ftpd"
  36. "github.com/drakkan/sftpgo/v2/internal/logger"
  37. "github.com/drakkan/sftpgo/v2/internal/mfa"
  38. "github.com/drakkan/sftpgo/v2/internal/sftpd"
  39. "github.com/drakkan/sftpgo/v2/internal/util"
  40. "github.com/drakkan/sftpgo/v2/internal/webdavd"
  41. )
  42. const (
  43. logSender = "httpd"
  44. tokenPath = "/api/v2/token"
  45. logoutPath = "/api/v2/logout"
  46. userTokenPath = "/api/v2/user/token"
  47. userLogoutPath = "/api/v2/user/logout"
  48. activeConnectionsPath = "/api/v2/connections"
  49. quotasBasePath = "/api/v2/quotas"
  50. userPath = "/api/v2/users"
  51. versionPath = "/api/v2/version"
  52. folderPath = "/api/v2/folders"
  53. groupPath = "/api/v2/groups"
  54. serverStatusPath = "/api/v2/status"
  55. dumpDataPath = "/api/v2/dumpdata"
  56. loadDataPath = "/api/v2/loaddata"
  57. defenderHosts = "/api/v2/defender/hosts"
  58. adminPath = "/api/v2/admins"
  59. adminPwdPath = "/api/v2/admin/changepwd"
  60. adminProfilePath = "/api/v2/admin/profile"
  61. userPwdPath = "/api/v2/user/changepwd"
  62. userDirsPath = "/api/v2/user/dirs"
  63. userFilesPath = "/api/v2/user/files"
  64. userFileActionsPath = "/api/v2/user/file-actions"
  65. userStreamZipPath = "/api/v2/user/streamzip"
  66. userUploadFilePath = "/api/v2/user/files/upload"
  67. userFilesDirsMetadataPath = "/api/v2/user/files/metadata"
  68. apiKeysPath = "/api/v2/apikeys"
  69. adminTOTPConfigsPath = "/api/v2/admin/totp/configs"
  70. adminTOTPGeneratePath = "/api/v2/admin/totp/generate"
  71. adminTOTPValidatePath = "/api/v2/admin/totp/validate"
  72. adminTOTPSavePath = "/api/v2/admin/totp/save"
  73. admin2FARecoveryCodesPath = "/api/v2/admin/2fa/recoverycodes"
  74. userTOTPConfigsPath = "/api/v2/user/totp/configs"
  75. userTOTPGeneratePath = "/api/v2/user/totp/generate"
  76. userTOTPValidatePath = "/api/v2/user/totp/validate"
  77. userTOTPSavePath = "/api/v2/user/totp/save"
  78. user2FARecoveryCodesPath = "/api/v2/user/2fa/recoverycodes"
  79. userProfilePath = "/api/v2/user/profile"
  80. userSharesPath = "/api/v2/user/shares"
  81. retentionBasePath = "/api/v2/retention/users"
  82. retentionChecksPath = "/api/v2/retention/users/checks"
  83. metadataBasePath = "/api/v2/metadata/users"
  84. metadataChecksPath = "/api/v2/metadata/users/checks"
  85. fsEventsPath = "/api/v2/events/fs"
  86. providerEventsPath = "/api/v2/events/provider"
  87. sharesPath = "/api/v2/shares"
  88. eventActionsPath = "/api/v2/eventactions"
  89. eventRulesPath = "/api/v2/eventrules"
  90. rolesPath = "/api/v2/roles"
  91. healthzPath = "/healthz"
  92. robotsTxtPath = "/robots.txt"
  93. webRootPathDefault = "/"
  94. webBasePathDefault = "/web"
  95. webBasePathAdminDefault = "/web/admin"
  96. webBasePathClientDefault = "/web/client"
  97. webAdminSetupPathDefault = "/web/admin/setup"
  98. webAdminLoginPathDefault = "/web/admin/login"
  99. webAdminOIDCLoginPathDefault = "/web/admin/oidclogin"
  100. webOIDCRedirectPathDefault = "/web/oidc/redirect"
  101. webAdminTwoFactorPathDefault = "/web/admin/twofactor"
  102. webAdminTwoFactorRecoveryPathDefault = "/web/admin/twofactor-recovery"
  103. webLogoutPathDefault = "/web/admin/logout"
  104. webUsersPathDefault = "/web/admin/users"
  105. webUserPathDefault = "/web/admin/user"
  106. webConnectionsPathDefault = "/web/admin/connections"
  107. webFoldersPathDefault = "/web/admin/folders"
  108. webFolderPathDefault = "/web/admin/folder"
  109. webGroupsPathDefault = "/web/admin/groups"
  110. webGroupPathDefault = "/web/admin/group"
  111. webStatusPathDefault = "/web/admin/status"
  112. webAdminsPathDefault = "/web/admin/managers"
  113. webAdminPathDefault = "/web/admin/manager"
  114. webMaintenancePathDefault = "/web/admin/maintenance"
  115. webBackupPathDefault = "/web/admin/backup"
  116. webRestorePathDefault = "/web/admin/restore"
  117. webScanVFolderPathDefault = "/web/admin/quotas/scanfolder"
  118. webQuotaScanPathDefault = "/web/admin/quotas/scanuser"
  119. webChangeAdminPwdPathDefault = "/web/admin/changepwd"
  120. webAdminForgotPwdPathDefault = "/web/admin/forgot-password"
  121. webAdminResetPwdPathDefault = "/web/admin/reset-password"
  122. webAdminProfilePathDefault = "/web/admin/profile"
  123. webAdminMFAPathDefault = "/web/admin/mfa"
  124. webAdminEventRulesPathDefault = "/web/admin/eventrules"
  125. webAdminEventRulePathDefault = "/web/admin/eventrule"
  126. webAdminEventActionsPathDefault = "/web/admin/eventactions"
  127. webAdminEventActionPathDefault = "/web/admin/eventaction"
  128. webAdminRolesPathDefault = "/web/admin/roles"
  129. webAdminRolePathDefault = "/web/admin/role"
  130. webAdminTOTPGeneratePathDefault = "/web/admin/totp/generate"
  131. webAdminTOTPValidatePathDefault = "/web/admin/totp/validate"
  132. webAdminTOTPSavePathDefault = "/web/admin/totp/save"
  133. webAdminRecoveryCodesPathDefault = "/web/admin/recoverycodes"
  134. webTemplateUserDefault = "/web/admin/template/user"
  135. webTemplateFolderDefault = "/web/admin/template/folder"
  136. webDefenderPathDefault = "/web/admin/defender"
  137. webDefenderHostsPathDefault = "/web/admin/defender/hosts"
  138. webEventsPathDefault = "/web/admin/events"
  139. webEventsFsSearchPathDefault = "/web/admin/events/fs"
  140. webEventsProviderSearchPathDefault = "/web/admin/events/provider"
  141. webClientLoginPathDefault = "/web/client/login"
  142. webClientOIDCLoginPathDefault = "/web/client/oidclogin"
  143. webClientTwoFactorPathDefault = "/web/client/twofactor"
  144. webClientTwoFactorRecoveryPathDefault = "/web/client/twofactor-recovery"
  145. webClientFilesPathDefault = "/web/client/files"
  146. webClientFilePathDefault = "/web/client/file"
  147. webClientFileActionsPathDefault = "/web/client/file-actions"
  148. webClientSharesPathDefault = "/web/client/shares"
  149. webClientSharePathDefault = "/web/client/share"
  150. webClientEditFilePathDefault = "/web/client/editfile"
  151. webClientDirsPathDefault = "/web/client/dirs"
  152. webClientDownloadZipPathDefault = "/web/client/downloadzip"
  153. webClientProfilePathDefault = "/web/client/profile"
  154. webClientMFAPathDefault = "/web/client/mfa"
  155. webClientTOTPGeneratePathDefault = "/web/client/totp/generate"
  156. webClientTOTPValidatePathDefault = "/web/client/totp/validate"
  157. webClientTOTPSavePathDefault = "/web/client/totp/save"
  158. webClientRecoveryCodesPathDefault = "/web/client/recoverycodes"
  159. webChangeClientPwdPathDefault = "/web/client/changepwd"
  160. webClientLogoutPathDefault = "/web/client/logout"
  161. webClientPubSharesPathDefault = "/web/client/pubshares"
  162. webClientForgotPwdPathDefault = "/web/client/forgot-password"
  163. webClientResetPwdPathDefault = "/web/client/reset-password"
  164. webClientViewPDFPathDefault = "/web/client/viewpdf"
  165. webClientGetPDFPathDefault = "/web/client/getpdf"
  166. webStaticFilesPathDefault = "/static"
  167. webOpenAPIPathDefault = "/openapi"
  168. // MaxRestoreSize defines the max size for the loaddata input file
  169. MaxRestoreSize = 10485760 // 10 MB
  170. maxRequestSize = 1048576 // 1MB
  171. maxLoginBodySize = 262144 // 256 KB
  172. httpdMaxEditFileSize = 1048576 // 1 MB
  173. maxMultipartMem = 10485760 // 10 MB
  174. osWindows = "windows"
  175. otpHeaderCode = "X-SFTPGO-OTP"
  176. mTimeHeader = "X-SFTPGO-MTIME"
  177. )
  178. var (
  179. certMgr *common.CertManager
  180. cleanupTicker *time.Ticker
  181. cleanupDone chan bool
  182. invalidatedJWTTokens sync.Map
  183. csrfTokenAuth *jwtauth.JWTAuth
  184. webRootPath string
  185. webBasePath string
  186. webBaseAdminPath string
  187. webBaseClientPath string
  188. webOIDCRedirectPath string
  189. webAdminSetupPath string
  190. webAdminOIDCLoginPath string
  191. webAdminLoginPath string
  192. webAdminTwoFactorPath string
  193. webAdminTwoFactorRecoveryPath string
  194. webLogoutPath string
  195. webUsersPath string
  196. webUserPath string
  197. webConnectionsPath string
  198. webFoldersPath string
  199. webFolderPath string
  200. webGroupsPath string
  201. webGroupPath string
  202. webStatusPath string
  203. webAdminsPath string
  204. webAdminPath string
  205. webMaintenancePath string
  206. webBackupPath string
  207. webRestorePath string
  208. webScanVFolderPath string
  209. webQuotaScanPath string
  210. webAdminProfilePath string
  211. webAdminMFAPath string
  212. webAdminEventRulesPath string
  213. webAdminEventRulePath string
  214. webAdminEventActionsPath string
  215. webAdminEventActionPath string
  216. webAdminRolesPath string
  217. webAdminRolePath string
  218. webAdminTOTPGeneratePath string
  219. webAdminTOTPValidatePath string
  220. webAdminTOTPSavePath string
  221. webAdminRecoveryCodesPath string
  222. webChangeAdminPwdPath string
  223. webAdminForgotPwdPath string
  224. webAdminResetPwdPath string
  225. webTemplateUser string
  226. webTemplateFolder string
  227. webDefenderPath string
  228. webEventsPath string
  229. webEventsFsSearchPath string
  230. webEventsProviderSearchPath string
  231. webDefenderHostsPath string
  232. webClientLoginPath string
  233. webClientOIDCLoginPath string
  234. webClientTwoFactorPath string
  235. webClientTwoFactorRecoveryPath string
  236. webClientFilesPath string
  237. webClientFilePath string
  238. webClientFileActionsPath string
  239. webClientSharesPath string
  240. webClientSharePath string
  241. webClientEditFilePath string
  242. webClientDirsPath string
  243. webClientDownloadZipPath string
  244. webClientProfilePath string
  245. webChangeClientPwdPath string
  246. webClientMFAPath string
  247. webClientTOTPGeneratePath string
  248. webClientTOTPValidatePath string
  249. webClientTOTPSavePath string
  250. webClientRecoveryCodesPath string
  251. webClientPubSharesPath string
  252. webClientLogoutPath string
  253. webClientForgotPwdPath string
  254. webClientResetPwdPath string
  255. webClientViewPDFPath string
  256. webClientGetPDFPath string
  257. webStaticFilesPath string
  258. webOpenAPIPath string
  259. // max upload size for http clients, 1GB by default
  260. maxUploadFileSize = int64(1048576000)
  261. hideSupportLink bool
  262. installationCode string
  263. installationCodeHint string
  264. fnInstallationCodeResolver FnInstallationCodeResolver
  265. )
  266. func init() {
  267. updateWebAdminURLs("")
  268. updateWebClientURLs("")
  269. }
  270. // FnInstallationCodeResolver defines a method to get the installation code.
  271. // If the installation code cannot be resolved the provided default must be returned
  272. type FnInstallationCodeResolver func(defaultInstallationCode string) string
  273. // HTTPSProxyHeader defines an HTTPS proxy header as key/value.
  274. // For example Key could be "X-Forwarded-Proto" and Value "https"
  275. type HTTPSProxyHeader struct {
  276. Key string
  277. Value string
  278. }
  279. // SecurityConf allows to add some security related headers to HTTP responses and to restrict allowed hosts
  280. type SecurityConf struct {
  281. // Set to true to enable the security configurations
  282. Enabled bool `json:"enabled" mapstructure:"enabled"`
  283. // AllowedHosts is a list of fully qualified domain names that are allowed.
  284. // Default is empty list, which allows any and all host names.
  285. AllowedHosts []string `json:"allowed_hosts" mapstructure:"allowed_hosts"`
  286. // AllowedHostsAreRegex determines if the provided allowed hosts contains valid regular expressions
  287. AllowedHostsAreRegex bool `json:"allowed_hosts_are_regex" mapstructure:"allowed_hosts_are_regex"`
  288. // HostsProxyHeaders is a set of header keys that may hold a proxied hostname value for the request.
  289. HostsProxyHeaders []string `json:"hosts_proxy_headers" mapstructure:"hosts_proxy_headers"`
  290. // Set to true to redirect HTTP requests to HTTPS
  291. HTTPSRedirect bool `json:"https_redirect" mapstructure:"https_redirect"`
  292. // HTTPSHost defines the host name that is used to redirect HTTP requests to HTTPS.
  293. // Default is "", which indicates to use the same host.
  294. HTTPSHost string `json:"https_host" mapstructure:"https_host"`
  295. // HTTPSProxyHeaders is a list of header keys with associated values that would indicate a valid https request.
  296. HTTPSProxyHeaders []HTTPSProxyHeader `json:"https_proxy_headers" mapstructure:"https_proxy_headers"`
  297. // STSSeconds is the max-age of the Strict-Transport-Security header.
  298. // Default is 0, which would NOT include the header.
  299. STSSeconds int64 `json:"sts_seconds" mapstructure:"sts_seconds"`
  300. // If STSIncludeSubdomains is set to true, the "includeSubdomains" will be appended to the
  301. // Strict-Transport-Security header. Default is false.
  302. STSIncludeSubdomains bool `json:"sts_include_subdomains" mapstructure:"sts_include_subdomains"`
  303. // If STSPreload is set to true, the `preload` flag will be appended to the
  304. // Strict-Transport-Security header. Default is false.
  305. STSPreload bool `json:"sts_preload" mapstructure:"sts_preload"`
  306. // If ContentTypeNosniff is true, adds the X-Content-Type-Options header with the value "nosniff". Default is false.
  307. ContentTypeNosniff bool `json:"content_type_nosniff" mapstructure:"content_type_nosniff"`
  308. // ContentSecurityPolicy allows to set the Content-Security-Policy header value. Default is "".
  309. ContentSecurityPolicy string `json:"content_security_policy" mapstructure:"content_security_policy"`
  310. // PermissionsPolicy allows to set the Permissions-Policy header value. Default is "".
  311. PermissionsPolicy string `json:"permissions_policy" mapstructure:"permissions_policy"`
  312. // CrossOriginOpenerPolicy allows to set the `Cross-Origin-Opener-Policy` header value. Default is "".
  313. CrossOriginOpenerPolicy string `json:"cross_origin_opener_policy" mapstructure:"cross_origin_opener_policy"`
  314. // ExpectCTHeader allows to set the Expect-CT header value. Default is "".
  315. ExpectCTHeader string `json:"expect_ct_header" mapstructure:"expect_ct_header"`
  316. proxyHeaders []string
  317. }
  318. func (s *SecurityConf) updateProxyHeaders() {
  319. if !s.Enabled {
  320. s.proxyHeaders = nil
  321. return
  322. }
  323. s.proxyHeaders = s.HostsProxyHeaders
  324. for _, httpsProxyHeader := range s.HTTPSProxyHeaders {
  325. s.proxyHeaders = append(s.proxyHeaders, httpsProxyHeader.Key)
  326. }
  327. }
  328. func (s *SecurityConf) getHTTPSProxyHeaders() map[string]string {
  329. headers := make(map[string]string)
  330. for _, httpsProxyHeader := range s.HTTPSProxyHeaders {
  331. headers[httpsProxyHeader.Key] = httpsProxyHeader.Value
  332. }
  333. return headers
  334. }
  335. // UIBranding defines the supported customizations for the web UIs
  336. type UIBranding struct {
  337. // Name defines the text to show at the login page and as HTML title
  338. Name string `json:"name" mapstructure:"name"`
  339. // ShortName defines the name to show next to the logo image
  340. ShortName string `json:"short_name" mapstructure:"short_name"`
  341. // Path to your logo relative to "static_files_path".
  342. // For example, if you create a directory named "branding" inside the static dir and
  343. // put the "mylogo.png" file in it, you must set "/branding/mylogo.png" as logo path.
  344. LogoPath string `json:"logo_path" mapstructure:"logo_path"`
  345. // Path to the image to show on the login screen relative to "static_files_path"
  346. LoginImagePath string `json:"login_image_path" mapstructure:"login_image_path"`
  347. // Path to your favicon relative to "static_files_path"
  348. FaviconPath string `json:"favicon_path" mapstructure:"favicon_path"`
  349. // DisclaimerName defines the name for the link to your optional disclaimer
  350. DisclaimerName string `json:"disclaimer_name" mapstructure:"disclaimer_name"`
  351. // Path to the HTML page for your disclaimer relative to "static_files_path".
  352. DisclaimerPath string `json:"disclaimer_path" mapstructure:"disclaimer_path"`
  353. // Path to a custom CSS file, relative to "static_files_path", which replaces
  354. // the SB Admin2 default CSS. This is useful, for example, if you rebuild
  355. // SB Admin2 CSS to use custom colors
  356. DefaultCSS string `json:"default_css" mapstructure:"default_css"`
  357. // Additional CSS file paths, relative to "static_files_path", to include
  358. ExtraCSS []string `json:"extra_css" mapstructure:"extra_css"`
  359. }
  360. func (b *UIBranding) check() {
  361. if b.LogoPath != "" {
  362. b.LogoPath = util.CleanPath(b.LogoPath)
  363. } else {
  364. b.LogoPath = "/img/logo.png"
  365. }
  366. if b.LoginImagePath != "" {
  367. b.LoginImagePath = util.CleanPath(b.LoginImagePath)
  368. } else {
  369. b.LoginImagePath = "/img/login_image.png"
  370. }
  371. if b.FaviconPath != "" {
  372. b.FaviconPath = util.CleanPath(b.FaviconPath)
  373. } else {
  374. b.FaviconPath = "/favicon.ico"
  375. }
  376. if b.DisclaimerPath != "" {
  377. b.DisclaimerPath = util.CleanPath(b.DisclaimerPath)
  378. }
  379. if b.DefaultCSS != "" {
  380. b.DefaultCSS = util.CleanPath(b.DefaultCSS)
  381. } else {
  382. b.DefaultCSS = "/css/sb-admin-2.min.css"
  383. }
  384. for idx := range b.ExtraCSS {
  385. b.ExtraCSS[idx] = util.CleanPath(b.ExtraCSS[idx])
  386. }
  387. }
  388. // Branding defines the branding-related customizations supported
  389. type Branding struct {
  390. WebAdmin UIBranding `json:"web_admin" mapstructure:"web_admin"`
  391. WebClient UIBranding `json:"web_client" mapstructure:"web_client"`
  392. }
  393. // WebClientIntegration defines the configuration for an external Web Client integration
  394. type WebClientIntegration struct {
  395. // Files with these extensions can be sent to the configured URL
  396. FileExtensions []string `json:"file_extensions" mapstructure:"file_extensions"`
  397. // URL that will receive the files
  398. URL string `json:"url" mapstructure:"url"`
  399. }
  400. // Binding defines the configuration for a network listener
  401. type Binding struct {
  402. // The address to listen on. A blank value means listen on all available network interfaces.
  403. Address string `json:"address" mapstructure:"address"`
  404. // The port used for serving requests
  405. Port int `json:"port" mapstructure:"port"`
  406. // Enable the built-in admin interface.
  407. // You have to define TemplatesPath and StaticFilesPath for this to work
  408. EnableWebAdmin bool `json:"enable_web_admin" mapstructure:"enable_web_admin"`
  409. // Enable the built-in client interface.
  410. // You have to define TemplatesPath and StaticFilesPath for this to work
  411. EnableWebClient bool `json:"enable_web_client" mapstructure:"enable_web_client"`
  412. // Enable REST API
  413. EnableRESTAPI bool `json:"enable_rest_api" mapstructure:"enable_rest_api"`
  414. // Defines the login methods available for the WebAdmin and WebClient UIs:
  415. //
  416. // - 0 means any configured method: username/password login form and OIDC, if enabled
  417. // - 1 means OIDC for the WebAdmin UI
  418. // - 2 means OIDC for the WebClient UI
  419. // - 4 means login form for the WebAdmin UI
  420. // - 8 means login form for the WebClient UI
  421. //
  422. // You can combine the values. For example 3 means that you can only login using OIDC on
  423. // both WebClient and WebAdmin UI.
  424. EnabledLoginMethods int `json:"enabled_login_methods" mapstructure:"enabled_login_methods"`
  425. // you also need to provide a certificate for enabling HTTPS
  426. EnableHTTPS bool `json:"enable_https" mapstructure:"enable_https"`
  427. // Certificate and matching private key for this specific binding, if empty the global
  428. // ones will be used, if any
  429. CertificateFile string `json:"certificate_file" mapstructure:"certificate_file"`
  430. CertificateKeyFile string `json:"certificate_key_file" mapstructure:"certificate_key_file"`
  431. // Defines the minimum TLS version. 13 means TLS 1.3, default is TLS 1.2
  432. MinTLSVersion int `json:"min_tls_version" mapstructure:"min_tls_version"`
  433. // set to 1 to require client certificate authentication in addition to basic auth.
  434. // You need to define at least a certificate authority for this to work
  435. ClientAuthType int `json:"client_auth_type" mapstructure:"client_auth_type"`
  436. // TLSCipherSuites is a list of supported cipher suites for TLS version 1.2.
  437. // If CipherSuites is nil/empty, a default list of secure cipher suites
  438. // is used, with a preference order based on hardware performance.
  439. // Note that TLS 1.3 ciphersuites are not configurable.
  440. // The supported ciphersuites names are defined here:
  441. //
  442. // https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L52
  443. //
  444. // any invalid name will be silently ignored.
  445. // The order matters, the ciphers listed first will be the preferred ones.
  446. TLSCipherSuites []string `json:"tls_cipher_suites" mapstructure:"tls_cipher_suites"`
  447. // List of IP addresses and IP ranges allowed to set client IP proxy headers and
  448. // X-Forwarded-Proto header.
  449. ProxyAllowed []string `json:"proxy_allowed" mapstructure:"proxy_allowed"`
  450. // Allowed client IP proxy header such as "X-Forwarded-For", "X-Real-IP"
  451. ClientIPProxyHeader string `json:"client_ip_proxy_header" mapstructure:"client_ip_proxy_header"`
  452. // Some client IP headers such as "X-Forwarded-For" can contain multiple IP address, this setting
  453. // define the position to trust starting from the right. For example if we have:
  454. // "10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1" and the depth is 0, SFTPGo will use "13.0.0.1"
  455. // as client IP, if depth is 1, "12.0.0.1" will be used and so on
  456. ClientIPHeaderDepth int `json:"client_ip_header_depth" mapstructure:"client_ip_header_depth"`
  457. // If both web admin and web client are enabled each login page will show a link
  458. // to the other one. This setting allows to hide this link:
  459. // - 0 login links are displayed on both admin and client login page. This is the default
  460. // - 1 the login link to the web client login page is hidden on admin login page
  461. // - 2 the login link to the web admin login page is hidden on client login page
  462. // The flags can be combined, for example 3 will disable both login links.
  463. HideLoginURL int `json:"hide_login_url" mapstructure:"hide_login_url"`
  464. // Enable the built-in OpenAPI renderer
  465. RenderOpenAPI bool `json:"render_openapi" mapstructure:"render_openapi"`
  466. // Enabling web client integrations you can render or modify the files with the specified
  467. // extensions using an external tool.
  468. WebClientIntegrations []WebClientIntegration `json:"web_client_integrations" mapstructure:"web_client_integrations"`
  469. // Defining an OIDC configuration the web admin and web client UI will use OpenID to authenticate users.
  470. OIDC OIDC `json:"oidc" mapstructure:"oidc"`
  471. // Security defines security headers to add to HTTP responses and allows to restrict allowed hosts
  472. Security SecurityConf `json:"security" mapstructure:"security"`
  473. // Branding defines customizations to suit your brand
  474. Branding Branding `json:"branding" mapstructure:"branding"`
  475. allowHeadersFrom []func(net.IP) bool
  476. }
  477. func (b *Binding) checkWebClientIntegrations() {
  478. var integrations []WebClientIntegration
  479. for _, integration := range b.WebClientIntegrations {
  480. if integration.URL != "" && len(integration.FileExtensions) > 0 {
  481. integrations = append(integrations, integration)
  482. }
  483. }
  484. b.WebClientIntegrations = integrations
  485. }
  486. func (b *Binding) checkBranding() {
  487. b.Branding.WebAdmin.check()
  488. b.Branding.WebClient.check()
  489. if b.Branding.WebAdmin.Name == "" {
  490. b.Branding.WebAdmin.Name = "SFTPGo WebAdmin"
  491. }
  492. if b.Branding.WebAdmin.ShortName == "" {
  493. b.Branding.WebAdmin.ShortName = "WebAdmin"
  494. }
  495. if b.Branding.WebClient.Name == "" {
  496. b.Branding.WebClient.Name = "SFTPGo WebClient"
  497. }
  498. if b.Branding.WebClient.ShortName == "" {
  499. b.Branding.WebClient.ShortName = "WebClient"
  500. }
  501. }
  502. func (b *Binding) parseAllowedProxy() error {
  503. if filepath.IsAbs(b.Address) && len(b.ProxyAllowed) > 0 {
  504. // unix domain socket
  505. b.allowHeadersFrom = []func(net.IP) bool{func(ip net.IP) bool { return true }}
  506. return nil
  507. }
  508. allowedFuncs, err := util.ParseAllowedIPAndRanges(b.ProxyAllowed)
  509. if err != nil {
  510. return err
  511. }
  512. b.allowHeadersFrom = allowedFuncs
  513. return nil
  514. }
  515. // GetAddress returns the binding address
  516. func (b *Binding) GetAddress() string {
  517. return fmt.Sprintf("%s:%d", b.Address, b.Port)
  518. }
  519. // IsValid returns true if the binding is valid
  520. func (b *Binding) IsValid() bool {
  521. if !b.EnableRESTAPI && !b.EnableWebAdmin && !b.EnableWebClient {
  522. return false
  523. }
  524. if b.Port > 0 {
  525. return true
  526. }
  527. if filepath.IsAbs(b.Address) && runtime.GOOS != osWindows {
  528. return true
  529. }
  530. return false
  531. }
  532. func (b *Binding) isWebAdminOIDCLoginDisabled() bool {
  533. if b.EnableWebAdmin {
  534. if b.EnabledLoginMethods == 0 {
  535. return false
  536. }
  537. return b.EnabledLoginMethods&1 == 0
  538. }
  539. return false
  540. }
  541. func (b *Binding) isWebClientOIDCLoginDisabled() bool {
  542. if b.EnableWebClient {
  543. if b.EnabledLoginMethods == 0 {
  544. return false
  545. }
  546. return b.EnabledLoginMethods&2 == 0
  547. }
  548. return false
  549. }
  550. func (b *Binding) isWebAdminLoginFormDisabled() bool {
  551. if b.EnableWebAdmin {
  552. if b.EnabledLoginMethods == 0 {
  553. return false
  554. }
  555. return b.EnabledLoginMethods&4 == 0
  556. }
  557. return false
  558. }
  559. func (b *Binding) isWebClientLoginFormDisabled() bool {
  560. if b.EnableWebClient {
  561. if b.EnabledLoginMethods == 0 {
  562. return false
  563. }
  564. return b.EnabledLoginMethods&8 == 0
  565. }
  566. return false
  567. }
  568. func (b *Binding) checkLoginMethods() error {
  569. if b.isWebAdminLoginFormDisabled() && b.isWebAdminOIDCLoginDisabled() {
  570. return errors.New("no login method available for WebAdmin UI")
  571. }
  572. if !b.isWebAdminOIDCLoginDisabled() {
  573. if b.isWebAdminLoginFormDisabled() && !b.OIDC.hasRoles() {
  574. return errors.New("no login method available for WebAdmin UI")
  575. }
  576. }
  577. if b.isWebClientLoginFormDisabled() && b.isWebClientOIDCLoginDisabled() {
  578. return errors.New("no login method available for WebClient UI")
  579. }
  580. if !b.isWebClientOIDCLoginDisabled() {
  581. if b.isWebClientLoginFormDisabled() && !b.OIDC.isEnabled() {
  582. return errors.New("no login method available for WebClient UI")
  583. }
  584. }
  585. return nil
  586. }
  587. func (b *Binding) showAdminLoginURL() bool {
  588. if !b.EnableWebAdmin {
  589. return false
  590. }
  591. if b.HideLoginURL&2 != 0 {
  592. return false
  593. }
  594. return true
  595. }
  596. func (b *Binding) showClientLoginURL() bool {
  597. if !b.EnableWebClient {
  598. return false
  599. }
  600. if b.HideLoginURL&1 != 0 {
  601. return false
  602. }
  603. return true
  604. }
  605. type defenderStatus struct {
  606. IsActive bool `json:"is_active"`
  607. }
  608. // ServicesStatus keep the state of the running services
  609. type ServicesStatus struct {
  610. SSH sftpd.ServiceStatus `json:"ssh"`
  611. FTP ftpd.ServiceStatus `json:"ftp"`
  612. WebDAV webdavd.ServiceStatus `json:"webdav"`
  613. DataProvider dataprovider.ProviderStatus `json:"data_provider"`
  614. Defender defenderStatus `json:"defender"`
  615. MFA mfa.ServiceStatus `json:"mfa"`
  616. }
  617. // SetupConfig defines the configuration parameters for the initial web admin setup
  618. type SetupConfig struct {
  619. // Installation code to require when creating the first admin account.
  620. // As for the other configurations, this value is read at SFTPGo startup and not at runtime
  621. // even if set using an environment variable.
  622. // This is not a license key or similar, the purpose here is to prevent anyone who can access
  623. // to the initial setup screen from creating an admin user
  624. InstallationCode string `json:"installation_code" mapstructure:"installation_code"`
  625. // Description for the installation code input field
  626. InstallationCodeHint string `json:"installation_code_hint" mapstructure:"installation_code_hint"`
  627. }
  628. // CorsConfig defines the CORS configuration
  629. type CorsConfig struct {
  630. AllowedOrigins []string `json:"allowed_origins" mapstructure:"allowed_origins"`
  631. AllowedMethods []string `json:"allowed_methods" mapstructure:"allowed_methods"`
  632. AllowedHeaders []string `json:"allowed_headers" mapstructure:"allowed_headers"`
  633. ExposedHeaders []string `json:"exposed_headers" mapstructure:"exposed_headers"`
  634. AllowCredentials bool `json:"allow_credentials" mapstructure:"allow_credentials"`
  635. Enabled bool `json:"enabled" mapstructure:"enabled"`
  636. MaxAge int `json:"max_age" mapstructure:"max_age"`
  637. OptionsPassthrough bool `json:"options_passthrough" mapstructure:"options_passthrough"`
  638. OptionsSuccessStatus int `json:"options_success_status" mapstructure:"options_success_status"`
  639. AllowPrivateNetwork bool `json:"allow_private_network" mapstructure:"allow_private_network"`
  640. }
  641. // Conf httpd daemon configuration
  642. type Conf struct {
  643. // Addresses and ports to bind to
  644. Bindings []Binding `json:"bindings" mapstructure:"bindings"`
  645. // Path to the HTML web templates. This can be an absolute path or a path relative to the config dir
  646. TemplatesPath string `json:"templates_path" mapstructure:"templates_path"`
  647. // Path to the static files for the web interface. This can be an absolute path or a path relative to the config dir.
  648. // If both TemplatesPath and StaticFilesPath are empty the built-in web interface will be disabled
  649. StaticFilesPath string `json:"static_files_path" mapstructure:"static_files_path"`
  650. // Path to the backup directory. This can be an absolute path or a path relative to the config dir
  651. //BackupsPath string `json:"backups_path" mapstructure:"backups_path"`
  652. // Path to the directory that contains the OpenAPI schema and the default renderer.
  653. // This can be an absolute path or a path relative to the config dir
  654. OpenAPIPath string `json:"openapi_path" mapstructure:"openapi_path"`
  655. // Defines a base URL for the web admin and client interfaces. If empty web admin and client resources will
  656. // be available at the root ("/") URI. If defined it must be an absolute URI or it will be ignored.
  657. WebRoot string `json:"web_root" mapstructure:"web_root"`
  658. // If files containing a certificate and matching private key for the server are provided you can enable
  659. // HTTPS connections for the configured bindings.
  660. // Certificate and key files can be reloaded on demand sending a "SIGHUP" signal on Unix based systems and a
  661. // "paramchange" request to the running service on Windows.
  662. CertificateFile string `json:"certificate_file" mapstructure:"certificate_file"`
  663. CertificateKeyFile string `json:"certificate_key_file" mapstructure:"certificate_key_file"`
  664. // CACertificates defines the set of root certificate authorities to be used to verify client certificates.
  665. CACertificates []string `json:"ca_certificates" mapstructure:"ca_certificates"`
  666. // CARevocationLists defines a set a revocation lists, one for each root CA, to be used to check
  667. // if a client certificate has been revoked
  668. CARevocationLists []string `json:"ca_revocation_lists" mapstructure:"ca_revocation_lists"`
  669. // SigningPassphrase defines the passphrase to use to derive the signing key for JWT and CSRF tokens.
  670. // If empty a random signing key will be generated each time SFTPGo starts. If you set a
  671. // signing passphrase you should consider rotating it periodically for added security
  672. SigningPassphrase string `json:"signing_passphrase" mapstructure:"signing_passphrase"`
  673. // TokenValidation allows to define how to validate JWT tokens, cookies and CSRF tokens.
  674. // By default all the available security checks are enabled. Set to 1 to disable the requirement
  675. // that a token must be used by the same IP for which it was issued.
  676. TokenValidation int `json:"token_validation" mapstructure:"token_validation"`
  677. // MaxUploadFileSize Defines the maximum request body size, in bytes, for Web Client/API HTTP upload requests.
  678. // 0 means no limit
  679. MaxUploadFileSize int64 `json:"max_upload_file_size" mapstructure:"max_upload_file_size"`
  680. // CORS configuration
  681. Cors CorsConfig `json:"cors" mapstructure:"cors"`
  682. // Initial setup configuration
  683. Setup SetupConfig `json:"setup" mapstructure:"setup"`
  684. // If enabled, the link to the sponsors section will not appear on the setup screen page
  685. HideSupportLink bool `json:"hide_support_link" mapstructure:"hide_support_link"`
  686. }
  687. type apiResponse struct {
  688. Error string `json:"error,omitempty"`
  689. Message string `json:"message"`
  690. }
  691. // ShouldBind returns true if there is at least a valid binding
  692. func (c *Conf) ShouldBind() bool {
  693. for _, binding := range c.Bindings {
  694. if binding.IsValid() {
  695. return true
  696. }
  697. }
  698. return false
  699. }
  700. func (c *Conf) isWebAdminEnabled() bool {
  701. for _, binding := range c.Bindings {
  702. if binding.EnableWebAdmin {
  703. return true
  704. }
  705. }
  706. return false
  707. }
  708. func (c *Conf) isWebClientEnabled() bool {
  709. for _, binding := range c.Bindings {
  710. if binding.EnableWebClient {
  711. return true
  712. }
  713. }
  714. return false
  715. }
  716. func (c *Conf) checkRequiredDirs(staticFilesPath, templatesPath string) error {
  717. if (c.isWebAdminEnabled() || c.isWebClientEnabled()) && (staticFilesPath == "" || templatesPath == "") {
  718. return fmt.Errorf("required directory is invalid, static file path: %#v template path: %#v",
  719. staticFilesPath, templatesPath)
  720. }
  721. return nil
  722. }
  723. func (c *Conf) getRedacted() Conf {
  724. redacted := "[redacted]"
  725. conf := *c
  726. if conf.SigningPassphrase != "" {
  727. conf.SigningPassphrase = redacted
  728. }
  729. if conf.Setup.InstallationCode != "" {
  730. conf.Setup.InstallationCode = redacted
  731. }
  732. conf.Bindings = nil
  733. for _, binding := range c.Bindings {
  734. if binding.OIDC.ClientID != "" {
  735. binding.OIDC.ClientID = redacted
  736. }
  737. if binding.OIDC.ClientSecret != "" {
  738. binding.OIDC.ClientSecret = redacted
  739. }
  740. conf.Bindings = append(conf.Bindings, binding)
  741. }
  742. return conf
  743. }
  744. func (c *Conf) getKeyPairs(configDir string) []common.TLSKeyPair {
  745. var keyPairs []common.TLSKeyPair
  746. for _, binding := range c.Bindings {
  747. certificateFile := getConfigPath(binding.CertificateFile, configDir)
  748. certificateKeyFile := getConfigPath(binding.CertificateKeyFile, configDir)
  749. if certificateFile != "" && certificateKeyFile != "" {
  750. keyPairs = append(keyPairs, common.TLSKeyPair{
  751. Cert: certificateFile,
  752. Key: certificateKeyFile,
  753. ID: binding.GetAddress(),
  754. })
  755. }
  756. }
  757. certificateFile := getConfigPath(c.CertificateFile, configDir)
  758. certificateKeyFile := getConfigPath(c.CertificateKeyFile, configDir)
  759. if certificateFile != "" && certificateKeyFile != "" {
  760. keyPairs = append(keyPairs, common.TLSKeyPair{
  761. Cert: certificateFile,
  762. Key: certificateKeyFile,
  763. ID: common.DefaultTLSKeyPaidID,
  764. })
  765. }
  766. return keyPairs
  767. }
  768. func (c *Conf) setTokenValidationMode() {
  769. if c.TokenValidation == 1 {
  770. tokenValidationMode = tokenValidationNoIPMatch
  771. } else {
  772. tokenValidationMode = tokenValidationFull
  773. }
  774. }
  775. // Initialize configures and starts the HTTP server
  776. func (c *Conf) Initialize(configDir string, isShared int) error {
  777. logger.Info(logSender, "", "initializing HTTP server with config %+v", c.getRedacted())
  778. resetCodesMgr = newResetCodeManager(isShared)
  779. oidcMgr = newOIDCManager(isShared)
  780. staticFilesPath := util.FindSharedDataPath(c.StaticFilesPath, configDir)
  781. templatesPath := util.FindSharedDataPath(c.TemplatesPath, configDir)
  782. openAPIPath := util.FindSharedDataPath(c.OpenAPIPath, configDir)
  783. if err := c.checkRequiredDirs(staticFilesPath, templatesPath); err != nil {
  784. return err
  785. }
  786. if c.isWebAdminEnabled() {
  787. updateWebAdminURLs(c.WebRoot)
  788. loadAdminTemplates(templatesPath)
  789. } else {
  790. logger.Info(logSender, "", "built-in web admin interface disabled")
  791. }
  792. if c.isWebClientEnabled() {
  793. updateWebClientURLs(c.WebRoot)
  794. loadClientTemplates(templatesPath)
  795. } else {
  796. logger.Info(logSender, "", "built-in web client interface disabled")
  797. }
  798. keyPairs := c.getKeyPairs(configDir)
  799. if len(keyPairs) > 0 {
  800. mgr, err := common.NewCertManager(keyPairs, configDir, logSender)
  801. if err != nil {
  802. return err
  803. }
  804. mgr.SetCACertificates(c.CACertificates)
  805. if err := mgr.LoadRootCAs(); err != nil {
  806. return err
  807. }
  808. mgr.SetCARevocationLists(c.CARevocationLists)
  809. if err := mgr.LoadCRLs(); err != nil {
  810. return err
  811. }
  812. certMgr = mgr
  813. }
  814. csrfTokenAuth = jwtauth.New(jwa.HS256.String(), getSigningKey(c.SigningPassphrase), nil)
  815. hideSupportLink = c.HideSupportLink
  816. exitChannel := make(chan error, 1)
  817. for _, binding := range c.Bindings {
  818. if !binding.IsValid() {
  819. continue
  820. }
  821. if err := binding.parseAllowedProxy(); err != nil {
  822. return err
  823. }
  824. binding.checkWebClientIntegrations()
  825. binding.checkBranding()
  826. binding.Security.updateProxyHeaders()
  827. go func(b Binding) {
  828. if err := b.OIDC.initialize(); err != nil {
  829. exitChannel <- err
  830. return
  831. }
  832. if err := b.checkLoginMethods(); err != nil {
  833. exitChannel <- err
  834. return
  835. }
  836. server := newHttpdServer(b, staticFilesPath, c.SigningPassphrase, c.Cors, openAPIPath)
  837. server.setShared(isShared)
  838. exitChannel <- server.listenAndServe()
  839. }(binding)
  840. }
  841. maxUploadFileSize = c.MaxUploadFileSize
  842. installationCode = c.Setup.InstallationCode
  843. installationCodeHint = c.Setup.InstallationCodeHint
  844. startCleanupTicker(tokenDuration / 2)
  845. c.setTokenValidationMode()
  846. return <-exitChannel
  847. }
  848. func isWebRequest(r *http.Request) bool {
  849. return strings.HasPrefix(r.RequestURI, webBasePath+"/")
  850. }
  851. func isWebClientRequest(r *http.Request) bool {
  852. return strings.HasPrefix(r.RequestURI, webBaseClientPath+"/")
  853. }
  854. // ReloadCertificateMgr reloads the certificate manager
  855. func ReloadCertificateMgr() error {
  856. if certMgr != nil {
  857. return certMgr.Reload()
  858. }
  859. return nil
  860. }
  861. func getConfigPath(name, configDir string) string {
  862. if !util.IsFileInputValid(name) {
  863. return ""
  864. }
  865. if name != "" && !filepath.IsAbs(name) {
  866. return filepath.Join(configDir, name)
  867. }
  868. return name
  869. }
  870. func getServicesStatus() *ServicesStatus {
  871. status := &ServicesStatus{
  872. SSH: sftpd.GetStatus(),
  873. FTP: ftpd.GetStatus(),
  874. WebDAV: webdavd.GetStatus(),
  875. DataProvider: dataprovider.GetProviderStatus(),
  876. Defender: defenderStatus{
  877. IsActive: common.Config.DefenderConfig.Enabled,
  878. },
  879. MFA: mfa.GetStatus(),
  880. }
  881. return status
  882. }
  883. func fileServer(r chi.Router, path string, root http.FileSystem) {
  884. if path != "/" && path[len(path)-1] != '/' {
  885. r.Get(path, http.RedirectHandler(path+"/", http.StatusMovedPermanently).ServeHTTP)
  886. path += "/"
  887. }
  888. path += "*"
  889. r.Get(path, func(w http.ResponseWriter, r *http.Request) {
  890. rctx := chi.RouteContext(r.Context())
  891. pathPrefix := strings.TrimSuffix(rctx.RoutePattern(), "/*")
  892. fs := http.StripPrefix(pathPrefix, http.FileServer(root))
  893. fs.ServeHTTP(w, r)
  894. })
  895. }
  896. func updateWebClientURLs(baseURL string) {
  897. if !path.IsAbs(baseURL) {
  898. baseURL = "/"
  899. }
  900. webRootPath = path.Join(baseURL, webRootPathDefault)
  901. webBasePath = path.Join(baseURL, webBasePathDefault)
  902. webBaseClientPath = path.Join(baseURL, webBasePathClientDefault)
  903. webOIDCRedirectPath = path.Join(baseURL, webOIDCRedirectPathDefault)
  904. webClientLoginPath = path.Join(baseURL, webClientLoginPathDefault)
  905. webClientOIDCLoginPath = path.Join(baseURL, webClientOIDCLoginPathDefault)
  906. webClientTwoFactorPath = path.Join(baseURL, webClientTwoFactorPathDefault)
  907. webClientTwoFactorRecoveryPath = path.Join(baseURL, webClientTwoFactorRecoveryPathDefault)
  908. webClientFilesPath = path.Join(baseURL, webClientFilesPathDefault)
  909. webClientFilePath = path.Join(baseURL, webClientFilePathDefault)
  910. webClientFileActionsPath = path.Join(baseURL, webClientFileActionsPathDefault)
  911. webClientSharesPath = path.Join(baseURL, webClientSharesPathDefault)
  912. webClientPubSharesPath = path.Join(baseURL, webClientPubSharesPathDefault)
  913. webClientSharePath = path.Join(baseURL, webClientSharePathDefault)
  914. webClientEditFilePath = path.Join(baseURL, webClientEditFilePathDefault)
  915. webClientDirsPath = path.Join(baseURL, webClientDirsPathDefault)
  916. webClientDownloadZipPath = path.Join(baseURL, webClientDownloadZipPathDefault)
  917. webClientProfilePath = path.Join(baseURL, webClientProfilePathDefault)
  918. webChangeClientPwdPath = path.Join(baseURL, webChangeClientPwdPathDefault)
  919. webClientLogoutPath = path.Join(baseURL, webClientLogoutPathDefault)
  920. webClientMFAPath = path.Join(baseURL, webClientMFAPathDefault)
  921. webClientTOTPGeneratePath = path.Join(baseURL, webClientTOTPGeneratePathDefault)
  922. webClientTOTPValidatePath = path.Join(baseURL, webClientTOTPValidatePathDefault)
  923. webClientTOTPSavePath = path.Join(baseURL, webClientTOTPSavePathDefault)
  924. webClientRecoveryCodesPath = path.Join(baseURL, webClientRecoveryCodesPathDefault)
  925. webClientForgotPwdPath = path.Join(baseURL, webClientForgotPwdPathDefault)
  926. webClientResetPwdPath = path.Join(baseURL, webClientResetPwdPathDefault)
  927. webClientViewPDFPath = path.Join(baseURL, webClientViewPDFPathDefault)
  928. webClientGetPDFPath = path.Join(baseURL, webClientGetPDFPathDefault)
  929. }
  930. func updateWebAdminURLs(baseURL string) {
  931. if !path.IsAbs(baseURL) {
  932. baseURL = "/"
  933. }
  934. webRootPath = path.Join(baseURL, webRootPathDefault)
  935. webBasePath = path.Join(baseURL, webBasePathDefault)
  936. webBaseAdminPath = path.Join(baseURL, webBasePathAdminDefault)
  937. webOIDCRedirectPath = path.Join(baseURL, webOIDCRedirectPathDefault)
  938. webAdminSetupPath = path.Join(baseURL, webAdminSetupPathDefault)
  939. webAdminLoginPath = path.Join(baseURL, webAdminLoginPathDefault)
  940. webAdminOIDCLoginPath = path.Join(baseURL, webAdminOIDCLoginPathDefault)
  941. webAdminTwoFactorPath = path.Join(baseURL, webAdminTwoFactorPathDefault)
  942. webAdminTwoFactorRecoveryPath = path.Join(baseURL, webAdminTwoFactorRecoveryPathDefault)
  943. webLogoutPath = path.Join(baseURL, webLogoutPathDefault)
  944. webUsersPath = path.Join(baseURL, webUsersPathDefault)
  945. webUserPath = path.Join(baseURL, webUserPathDefault)
  946. webConnectionsPath = path.Join(baseURL, webConnectionsPathDefault)
  947. webFoldersPath = path.Join(baseURL, webFoldersPathDefault)
  948. webFolderPath = path.Join(baseURL, webFolderPathDefault)
  949. webGroupsPath = path.Join(baseURL, webGroupsPathDefault)
  950. webGroupPath = path.Join(baseURL, webGroupPathDefault)
  951. webStatusPath = path.Join(baseURL, webStatusPathDefault)
  952. webAdminsPath = path.Join(baseURL, webAdminsPathDefault)
  953. webAdminPath = path.Join(baseURL, webAdminPathDefault)
  954. webMaintenancePath = path.Join(baseURL, webMaintenancePathDefault)
  955. webBackupPath = path.Join(baseURL, webBackupPathDefault)
  956. webRestorePath = path.Join(baseURL, webRestorePathDefault)
  957. webScanVFolderPath = path.Join(baseURL, webScanVFolderPathDefault)
  958. webQuotaScanPath = path.Join(baseURL, webQuotaScanPathDefault)
  959. webChangeAdminPwdPath = path.Join(baseURL, webChangeAdminPwdPathDefault)
  960. webAdminForgotPwdPath = path.Join(baseURL, webAdminForgotPwdPathDefault)
  961. webAdminResetPwdPath = path.Join(baseURL, webAdminResetPwdPathDefault)
  962. webAdminProfilePath = path.Join(baseURL, webAdminProfilePathDefault)
  963. webAdminMFAPath = path.Join(baseURL, webAdminMFAPathDefault)
  964. webAdminEventRulesPath = path.Join(baseURL, webAdminEventRulesPathDefault)
  965. webAdminEventRulePath = path.Join(baseURL, webAdminEventRulePathDefault)
  966. webAdminEventActionsPath = path.Join(baseURL, webAdminEventActionsPathDefault)
  967. webAdminEventActionPath = path.Join(baseURL, webAdminEventActionPathDefault)
  968. webAdminRolesPath = path.Join(baseURL, webAdminRolesPathDefault)
  969. webAdminRolePath = path.Join(baseURL, webAdminRolePathDefault)
  970. webAdminTOTPGeneratePath = path.Join(baseURL, webAdminTOTPGeneratePathDefault)
  971. webAdminTOTPValidatePath = path.Join(baseURL, webAdminTOTPValidatePathDefault)
  972. webAdminTOTPSavePath = path.Join(baseURL, webAdminTOTPSavePathDefault)
  973. webAdminRecoveryCodesPath = path.Join(baseURL, webAdminRecoveryCodesPathDefault)
  974. webTemplateUser = path.Join(baseURL, webTemplateUserDefault)
  975. webTemplateFolder = path.Join(baseURL, webTemplateFolderDefault)
  976. webDefenderHostsPath = path.Join(baseURL, webDefenderHostsPathDefault)
  977. webDefenderPath = path.Join(baseURL, webDefenderPathDefault)
  978. webEventsPath = path.Join(baseURL, webEventsPathDefault)
  979. webEventsFsSearchPath = path.Join(baseURL, webEventsFsSearchPathDefault)
  980. webEventsProviderSearchPath = path.Join(baseURL, webEventsProviderSearchPathDefault)
  981. webStaticFilesPath = path.Join(baseURL, webStaticFilesPathDefault)
  982. webOpenAPIPath = path.Join(baseURL, webOpenAPIPathDefault)
  983. }
  984. // GetHTTPRouter returns an HTTP handler suitable to use for test cases
  985. func GetHTTPRouter(b Binding) http.Handler {
  986. server := newHttpdServer(b, filepath.Join("..", "..", "static"), "", CorsConfig{}, filepath.Join("..", "..", "openapi"))
  987. server.initializeRouter()
  988. return server.router
  989. }
  990. // the ticker cannot be started/stopped from multiple goroutines
  991. func startCleanupTicker(duration time.Duration) {
  992. stopCleanupTicker()
  993. cleanupTicker = time.NewTicker(duration)
  994. cleanupDone = make(chan bool)
  995. go func() {
  996. counter := int64(0)
  997. for {
  998. select {
  999. case <-cleanupDone:
  1000. return
  1001. case <-cleanupTicker.C:
  1002. counter++
  1003. cleanupExpiredJWTTokens()
  1004. resetCodesMgr.Cleanup()
  1005. if counter%2 == 0 {
  1006. oidcMgr.cleanup()
  1007. }
  1008. }
  1009. }
  1010. }()
  1011. }
  1012. func stopCleanupTicker() {
  1013. if cleanupTicker != nil {
  1014. cleanupTicker.Stop()
  1015. cleanupDone <- true
  1016. cleanupTicker = nil
  1017. }
  1018. }
  1019. func cleanupExpiredJWTTokens() {
  1020. invalidatedJWTTokens.Range(func(key, value any) bool {
  1021. exp, ok := value.(time.Time)
  1022. if !ok || exp.Before(time.Now().UTC()) {
  1023. invalidatedJWTTokens.Delete(key)
  1024. }
  1025. return true
  1026. })
  1027. }
  1028. func getSigningKey(signingPassphrase string) []byte {
  1029. if signingPassphrase != "" {
  1030. sk := sha256.Sum256([]byte(signingPassphrase))
  1031. return sk[:]
  1032. }
  1033. return util.GenerateRandomBytes(32)
  1034. }
  1035. // SetInstallationCodeResolver sets a function to call to resolve the installation code
  1036. func SetInstallationCodeResolver(fn FnInstallationCodeResolver) {
  1037. fnInstallationCodeResolver = fn
  1038. }
  1039. func resolveInstallationCode() string {
  1040. if fnInstallationCodeResolver != nil {
  1041. return fnInstallationCodeResolver(installationCode)
  1042. }
  1043. return installationCode
  1044. }