confighandler.go 12 KB

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