confighandler.go 12 KB

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