confighandler.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. // Copyright (C) 2020 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package api
  7. import (
  8. "encoding/json"
  9. "io"
  10. "io/ioutil"
  11. "net/http"
  12. "github.com/julienschmidt/httprouter"
  13. "golang.org/x/crypto/bcrypt"
  14. "github.com/syncthing/syncthing/lib/config"
  15. "github.com/syncthing/syncthing/lib/protocol"
  16. "github.com/syncthing/syncthing/lib/util"
  17. )
  18. type configMuxBuilder struct {
  19. *httprouter.Router
  20. id protocol.DeviceID
  21. cfg config.Wrapper
  22. }
  23. func (c *configMuxBuilder) registerConfig(path string) {
  24. c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) {
  25. sendJSON(w, c.cfg.RawCopy())
  26. })
  27. c.HandlerFunc(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request) {
  28. c.adjustConfig(w, r)
  29. })
  30. }
  31. func (c *configMuxBuilder) registerConfigDeprecated(path string) {
  32. c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) {
  33. sendJSON(w, c.cfg.RawCopy())
  34. })
  35. c.HandlerFunc(http.MethodPost, path, func(w http.ResponseWriter, r *http.Request) {
  36. c.adjustConfig(w, r)
  37. })
  38. }
  39. func (c *configMuxBuilder) registerConfigInsync(path string) {
  40. c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) {
  41. sendJSON(w, map[string]bool{"configInSync": !c.cfg.RequiresRestart()})
  42. })
  43. }
  44. func (c *configMuxBuilder) registerConfigRequiresRestart(path string) {
  45. c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) {
  46. sendJSON(w, map[string]bool{"requiresRestart": c.cfg.RequiresRestart()})
  47. })
  48. }
  49. func (c *configMuxBuilder) registerFolders(path string) {
  50. c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) {
  51. sendJSON(w, c.cfg.FolderList())
  52. })
  53. c.HandlerFunc(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request) {
  54. data, err := unmarshalToRawMessages(r.Body)
  55. folders := make([]config.FolderConfiguration, len(data))
  56. defaultFolder := c.cfg.DefaultFolder()
  57. for i, bs := range data {
  58. folders[i] = defaultFolder.Copy()
  59. if err := json.Unmarshal(bs, &folders[i]); err != nil {
  60. http.Error(w, err.Error(), http.StatusBadRequest)
  61. return
  62. }
  63. }
  64. waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
  65. cfg.SetFolders(folders)
  66. })
  67. if err != nil {
  68. http.Error(w, err.Error(), http.StatusInternalServerError)
  69. return
  70. }
  71. c.finish(w, waiter)
  72. })
  73. c.HandlerFunc(http.MethodPost, path, func(w http.ResponseWriter, r *http.Request) {
  74. c.adjustFolder(w, r, c.cfg.DefaultFolder(), false)
  75. })
  76. }
  77. func (c *configMuxBuilder) registerDevices(path string) {
  78. c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) {
  79. sendJSON(w, c.cfg.DeviceList())
  80. })
  81. c.HandlerFunc(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request) {
  82. data, err := unmarshalToRawMessages(r.Body)
  83. devices := make([]config.DeviceConfiguration, len(data))
  84. defaultDevice := c.cfg.DefaultDevice()
  85. for i, bs := range data {
  86. devices[i] = defaultDevice.Copy()
  87. if err := json.Unmarshal(bs, &devices[i]); err != nil {
  88. http.Error(w, err.Error(), http.StatusBadRequest)
  89. return
  90. }
  91. }
  92. waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
  93. cfg.SetDevices(devices)
  94. })
  95. if err != nil {
  96. http.Error(w, err.Error(), http.StatusInternalServerError)
  97. return
  98. }
  99. c.finish(w, waiter)
  100. })
  101. c.HandlerFunc(http.MethodPost, path, func(w http.ResponseWriter, r *http.Request) {
  102. c.adjustDevice(w, r, c.cfg.DefaultDevice(), false)
  103. })
  104. }
  105. func (c *configMuxBuilder) registerFolder(path string) {
  106. c.Handle(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request, p httprouter.Params) {
  107. folder, ok := c.cfg.Folder(p.ByName("id"))
  108. if !ok {
  109. http.Error(w, "No folder with given ID", http.StatusNotFound)
  110. return
  111. }
  112. sendJSON(w, folder)
  113. })
  114. c.Handle(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
  115. c.adjustFolder(w, r, c.cfg.DefaultFolder(), false)
  116. })
  117. c.Handle(http.MethodPatch, path, func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
  118. folder, ok := c.cfg.Folder(p.ByName("id"))
  119. if !ok {
  120. http.Error(w, "No folder with given ID", http.StatusNotFound)
  121. return
  122. }
  123. c.adjustFolder(w, r, folder, false)
  124. })
  125. c.Handle(http.MethodDelete, path, func(w http.ResponseWriter, _ *http.Request, p httprouter.Params) {
  126. waiter, err := c.cfg.RemoveFolder(p.ByName("id"))
  127. if err != nil {
  128. http.Error(w, err.Error(), http.StatusBadRequest)
  129. return
  130. }
  131. c.finish(w, waiter)
  132. })
  133. }
  134. func (c *configMuxBuilder) registerDevice(path string) {
  135. deviceFromParams := func(w http.ResponseWriter, p httprouter.Params) (config.DeviceConfiguration, bool) {
  136. id, err := protocol.DeviceIDFromString(p.ByName("id"))
  137. if err != nil {
  138. http.Error(w, err.Error(), http.StatusBadRequest)
  139. return config.DeviceConfiguration{}, false
  140. }
  141. device, ok := c.cfg.Device(id)
  142. if !ok {
  143. http.Error(w, "No device with given ID", http.StatusNotFound)
  144. return config.DeviceConfiguration{}, false
  145. }
  146. return device, true
  147. }
  148. c.Handle(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request, p httprouter.Params) {
  149. if device, ok := deviceFromParams(w, p); ok {
  150. sendJSON(w, device)
  151. }
  152. })
  153. c.Handle(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
  154. c.adjustDevice(w, r, c.cfg.DefaultDevice(), false)
  155. })
  156. c.Handle(http.MethodPatch, path, func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
  157. if device, ok := deviceFromParams(w, p); ok {
  158. c.adjustDevice(w, r, device, false)
  159. }
  160. })
  161. c.Handle(http.MethodDelete, path, func(w http.ResponseWriter, _ *http.Request, p httprouter.Params) {
  162. id, err := protocol.DeviceIDFromString(p.ByName("id"))
  163. if err != nil {
  164. http.Error(w, err.Error(), http.StatusBadRequest)
  165. return
  166. }
  167. waiter, err := c.cfg.RemoveDevice(id)
  168. if err != nil {
  169. http.Error(w, err.Error(), http.StatusBadRequest)
  170. return
  171. }
  172. c.finish(w, waiter)
  173. })
  174. }
  175. func (c *configMuxBuilder) registerDefaultFolder(path string) {
  176. c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) {
  177. sendJSON(w, c.cfg.DefaultFolder())
  178. })
  179. c.HandlerFunc(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request) {
  180. var cfg config.FolderConfiguration
  181. util.SetDefaults(&cfg)
  182. c.adjustFolder(w, r, cfg, true)
  183. })
  184. c.HandlerFunc(http.MethodPatch, path, func(w http.ResponseWriter, r *http.Request) {
  185. c.adjustFolder(w, r, c.cfg.DefaultFolder(), true)
  186. })
  187. }
  188. func (c *configMuxBuilder) registerDefaultDevice(path string) {
  189. c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) {
  190. sendJSON(w, c.cfg.DefaultDevice())
  191. })
  192. c.HandlerFunc(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request) {
  193. var cfg config.DeviceConfiguration
  194. util.SetDefaults(&cfg)
  195. c.adjustDevice(w, r, cfg, true)
  196. })
  197. c.HandlerFunc(http.MethodPatch, path, func(w http.ResponseWriter, r *http.Request) {
  198. c.adjustDevice(w, r, c.cfg.DefaultDevice(), true)
  199. })
  200. }
  201. func (c *configMuxBuilder) registerOptions(path string) {
  202. c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) {
  203. sendJSON(w, c.cfg.Options())
  204. })
  205. c.HandlerFunc(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request) {
  206. var cfg config.OptionsConfiguration
  207. util.SetDefaults(&cfg)
  208. c.adjustOptions(w, r, cfg)
  209. })
  210. c.HandlerFunc(http.MethodPatch, path, func(w http.ResponseWriter, r *http.Request) {
  211. c.adjustOptions(w, r, c.cfg.Options())
  212. })
  213. }
  214. func (c *configMuxBuilder) registerLDAP(path string) {
  215. c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) {
  216. sendJSON(w, c.cfg.LDAP())
  217. })
  218. c.HandlerFunc(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request) {
  219. var cfg config.LDAPConfiguration
  220. util.SetDefaults(&cfg)
  221. c.adjustLDAP(w, r, cfg)
  222. })
  223. c.HandlerFunc(http.MethodPatch, path, func(w http.ResponseWriter, r *http.Request) {
  224. c.adjustLDAP(w, r, c.cfg.LDAP())
  225. })
  226. }
  227. func (c *configMuxBuilder) registerGUI(path string) {
  228. c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) {
  229. sendJSON(w, c.cfg.GUI())
  230. })
  231. c.HandlerFunc(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request) {
  232. var cfg config.GUIConfiguration
  233. util.SetDefaults(&cfg)
  234. c.adjustGUI(w, r, cfg)
  235. })
  236. c.HandlerFunc(http.MethodPatch, path, func(w http.ResponseWriter, r *http.Request) {
  237. c.adjustGUI(w, r, c.cfg.GUI())
  238. })
  239. }
  240. func (c *configMuxBuilder) adjustConfig(w http.ResponseWriter, r *http.Request) {
  241. to, err := config.ReadJSON(r.Body, c.id)
  242. r.Body.Close()
  243. if err != nil {
  244. l.Warnln("Decoding posted config:", err)
  245. http.Error(w, err.Error(), http.StatusBadRequest)
  246. return
  247. }
  248. var errMsg string
  249. var status int
  250. waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
  251. if to.GUI.Password, err = checkGUIPassword(cfg.GUI.Password, to.GUI.Password); err != nil {
  252. l.Warnln("bcrypting password:", err)
  253. errMsg = err.Error()
  254. status = http.StatusInternalServerError
  255. return
  256. }
  257. *cfg = to
  258. })
  259. if errMsg != "" {
  260. http.Error(w, errMsg, status)
  261. } else if err != nil {
  262. http.Error(w, err.Error(), http.StatusInternalServerError)
  263. return
  264. }
  265. c.finish(w, waiter)
  266. }
  267. func (c *configMuxBuilder) adjustFolder(w http.ResponseWriter, r *http.Request, folder config.FolderConfiguration, defaults bool) {
  268. if err := unmarshalTo(r.Body, &folder); err != nil {
  269. http.Error(w, err.Error(), http.StatusBadRequest)
  270. return
  271. }
  272. waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
  273. if defaults {
  274. cfg.Defaults.Folder = folder
  275. } else {
  276. cfg.SetFolder(folder)
  277. }
  278. })
  279. if err != nil {
  280. http.Error(w, err.Error(), http.StatusInternalServerError)
  281. return
  282. }
  283. c.finish(w, waiter)
  284. }
  285. func (c *configMuxBuilder) adjustDevice(w http.ResponseWriter, r *http.Request, device config.DeviceConfiguration, defaults bool) {
  286. if err := unmarshalTo(r.Body, &device); err != nil {
  287. http.Error(w, err.Error(), http.StatusBadRequest)
  288. return
  289. }
  290. waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
  291. if defaults {
  292. cfg.Defaults.Device = device
  293. } else {
  294. cfg.SetDevice(device)
  295. }
  296. })
  297. if err != nil {
  298. http.Error(w, err.Error(), http.StatusInternalServerError)
  299. return
  300. }
  301. c.finish(w, waiter)
  302. }
  303. func (c *configMuxBuilder) adjustOptions(w http.ResponseWriter, r *http.Request, opts config.OptionsConfiguration) {
  304. if err := unmarshalTo(r.Body, &opts); err != nil {
  305. http.Error(w, err.Error(), http.StatusBadRequest)
  306. return
  307. }
  308. waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
  309. cfg.Options = opts
  310. })
  311. if err != nil {
  312. http.Error(w, err.Error(), http.StatusInternalServerError)
  313. return
  314. }
  315. c.finish(w, waiter)
  316. }
  317. func (c *configMuxBuilder) adjustGUI(w http.ResponseWriter, r *http.Request, gui config.GUIConfiguration) {
  318. oldPassword := gui.Password
  319. err := unmarshalTo(r.Body, &gui)
  320. if err != nil {
  321. http.Error(w, err.Error(), http.StatusBadRequest)
  322. return
  323. }
  324. var errMsg string
  325. var status int
  326. waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
  327. if gui.Password, err = checkGUIPassword(oldPassword, gui.Password); err != nil {
  328. l.Warnln("bcrypting password:", err)
  329. errMsg = err.Error()
  330. status = http.StatusInternalServerError
  331. return
  332. }
  333. cfg.GUI = gui
  334. })
  335. if errMsg != "" {
  336. http.Error(w, errMsg, status)
  337. } else if err != nil {
  338. http.Error(w, err.Error(), http.StatusInternalServerError)
  339. return
  340. }
  341. c.finish(w, waiter)
  342. }
  343. func (c *configMuxBuilder) adjustLDAP(w http.ResponseWriter, r *http.Request, ldap config.LDAPConfiguration) {
  344. if err := unmarshalTo(r.Body, &ldap); err != nil {
  345. http.Error(w, err.Error(), http.StatusBadRequest)
  346. return
  347. }
  348. waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
  349. cfg.LDAP = ldap
  350. })
  351. if err != nil {
  352. http.Error(w, err.Error(), http.StatusInternalServerError)
  353. return
  354. }
  355. c.finish(w, waiter)
  356. }
  357. // Unmarshals the content of the given body and stores it in to (i.e. to must be a pointer).
  358. func unmarshalTo(body io.ReadCloser, to interface{}) error {
  359. bs, err := ioutil.ReadAll(body)
  360. body.Close()
  361. if err != nil {
  362. return err
  363. }
  364. return json.Unmarshal(bs, to)
  365. }
  366. func unmarshalToRawMessages(body io.ReadCloser) ([]json.RawMessage, error) {
  367. var data []json.RawMessage
  368. err := unmarshalTo(body, &data)
  369. return data, err
  370. }
  371. func checkGUIPassword(oldPassword, newPassword string) (string, error) {
  372. if newPassword == oldPassword {
  373. return newPassword, nil
  374. }
  375. hash, err := bcrypt.GenerateFromPassword([]byte(newPassword), 0)
  376. return string(hash), err
  377. }
  378. func (c *configMuxBuilder) finish(w http.ResponseWriter, waiter config.Waiter) {
  379. waiter.Wait()
  380. if err := c.cfg.Save(); err != nil {
  381. l.Warnln("Saving config:", err)
  382. http.Error(w, err.Error(), http.StatusInternalServerError)
  383. }
  384. }