From da5010d37aa064be0843f45e830fe7e4e7d9a184 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Thu, 26 May 2016 07:25:34 +0000 Subject: [PATCH] cmd/syncthing: Use API to generate API Key and folder ID (fixes #3179) Expose a random string generator in the API and use it when the GUI needs random strings for API key and folder ID. GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3192 --- cmd/syncthing/gui.go | 11 ++++ cmd/syncthing/gui_test.go | 50 +++++++++++++++++++ gui/default/syncthing/app.js | 13 ----- .../syncthing/core/syncthingController.js | 15 +++--- 4 files changed, 68 insertions(+), 21 deletions(-) diff --git a/cmd/syncthing/gui.go b/cmd/syncthing/gui.go index 2a092e92f..30fe22812 100644 --- a/cmd/syncthing/gui.go +++ b/cmd/syncthing/gui.go @@ -250,6 +250,7 @@ func (s *apiService) Serve() { getRestMux.HandleFunc("/rest/svc/deviceid", s.getDeviceID) // id getRestMux.HandleFunc("/rest/svc/lang", s.getLang) // - getRestMux.HandleFunc("/rest/svc/report", s.getReport) // - + getRestMux.HandleFunc("/rest/svc/random/string", s.getRandomString) // [length] getRestMux.HandleFunc("/rest/system/browse", s.getSystemBrowse) // current getRestMux.HandleFunc("/rest/system/config", s.getSystemConfig) // - getRestMux.HandleFunc("/rest/system/config/insync", s.getSystemConfigInsync) // - @@ -930,6 +931,16 @@ func (s *apiService) getReport(w http.ResponseWriter, r *http.Request) { sendJSON(w, reportData(s.cfg, s.model)) } +func (s *apiService) getRandomString(w http.ResponseWriter, r *http.Request) { + length := 32 + if val, _ := strconv.Atoi(r.URL.Query().Get("length")); val > 0 { + length = val + } + str := rand.String(length) + + sendJSON(w, map[string]string{"random": str}) +} + func (s *apiService) getDBIgnores(w http.ResponseWriter, r *http.Request) { qs := r.URL.Query() diff --git a/cmd/syncthing/gui_test.go b/cmd/syncthing/gui_test.go index 4e58dda8a..f97d6b8c3 100644 --- a/cmd/syncthing/gui_test.go +++ b/cmd/syncthing/gui_test.go @@ -9,6 +9,7 @@ package main import ( "bytes" "compress/gzip" + "encoding/json" "fmt" "io/ioutil" "net" @@ -570,3 +571,52 @@ func TestCSRFRequired(t *testing.T) { t.Fatal("Getting /rest/system/config with API key should succeed, not", resp.Status) } } + +func TestRandomString(t *testing.T) { + const testAPIKey = "foobarbaz" + cfg := new(mockedConfig) + cfg.gui.APIKey = testAPIKey + baseURL, err := startHTTP(cfg) + if err != nil { + t.Fatal(err) + } + cli := &http.Client{ + Timeout: time.Second, + } + + // The default should be to return a 32 character random string + + for _, url := range []string{"/rest/svc/random/string", "/rest/svc/random/string?length=-1", "/rest/svc/random/string?length=yo"} { + req, _ := http.NewRequest("GET", baseURL+url, nil) + req.Header.Set("X-API-Key", testAPIKey) + resp, err := cli.Do(req) + if err != nil { + t.Fatal(err) + } + + var res map[string]string + if err := json.NewDecoder(resp.Body).Decode(&res); err != nil { + t.Fatal(err) + } + if len(res["random"]) != 32 { + t.Errorf("Expected 32 random characters, got %q of length %d", res["random"], len(res["random"])) + } + } + + // We can ask for a different length if we like + + req, _ := http.NewRequest("GET", baseURL+"/rest/svc/random/string?length=27", nil) + req.Header.Set("X-API-Key", testAPIKey) + resp, err := cli.Do(req) + if err != nil { + t.Fatal(err) + } + + var res map[string]string + if err := json.NewDecoder(resp.Body).Decode(&res); err != nil { + t.Fatal(err) + } + if len(res["random"]) != 27 { + t.Errorf("Expected 27 random characters, got %q of length %d", res["random"], len(res["random"])) + } +} diff --git a/gui/default/syncthing/app.js b/gui/default/syncthing/app.js index 5df81b391..0c2370d1c 100644 --- a/gui/default/syncthing/app.js +++ b/gui/default/syncthing/app.js @@ -89,19 +89,6 @@ function decimals(val, num) { return decs; } -function randomString(len) { - var chars = '01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-'; - return randomStringFromCharset(len, chars); -} - -function randomStringFromCharset(len, charset) { - var result = ''; - for (var i = 0; i < len; i++) { - result += charset[Math.round(Math.random() * (charset.length - 1))]; - } - return result; -} - function isEmptyObject(obj) { var name; for (name in obj) { diff --git a/gui/default/syncthing/core/syncthingController.js b/gui/default/syncthing/core/syncthingController.js index aeee056c8..5722f5f4a 100755 --- a/gui/default/syncthing/core/syncthingController.js +++ b/gui/default/syncthing/core/syncthingController.js @@ -1196,7 +1196,6 @@ angular.module('syncthing.core') $scope.addFolder = function () { $scope.currentFolder = { selectedDevices: {}, - id: $scope.createRandomFolderId(), type: "readwrite", rescanIntervalS: 60, minDiskFreePct: 1, @@ -1213,7 +1212,10 @@ angular.module('syncthing.core') }; $scope.editingExisting = false; $scope.folderEditor.$setPristine(); - $('#editFolder').modal(); + $http.get(urlbase + '/svc/random/string?length=10').success(function (data) { + $scope.currentFolder.id = data.random.substr(0, 5) + '-' + data.random.substr(5, 5); + $('#editFolder').modal(); + }); }; $scope.addFolderAndShare = function (folder, folderLabel, device) { @@ -1406,7 +1408,9 @@ angular.module('syncthing.core') }; $scope.setAPIKey = function (cfg) { - cfg.apiKey = randomString(32); + $http.get(urlbase + '/svc/random/string?length=32').success(function (data) { + cfg.apiKey = data.random; + }); }; $scope.showURPreview = function () { @@ -1544,11 +1548,6 @@ angular.module('syncthing.core') return 'text'; }; - $scope.createRandomFolderId = function(){ - var charset = '2345679abcdefghijkmnopqrstuvwxyzACDEFGHJKLMNPQRSTUVWXYZ'; - return randomStringFromCharset(5, charset) + "-" + randomStringFromCharset(5, charset); - }; - $scope.themeName = function (theme) { return theme.replace('-', ' ').replace(/(?:^|\s)\S/g, function (a) { return a.toUpperCase();