confighandler.go 12 KB

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