Browse Source

cmd/syncthing: Conditionally enable CORS

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3541
LGTM: AudriusButkevicius
Laurent Etiemble 9 years ago
parent
commit
3990014073
3 changed files with 68 additions and 2 deletions
  1. 4 2
      cmd/syncthing/gui.go
  2. 3 0
      cmd/syncthing/gui_csrf.go
  3. 61 0
      cmd/syncthing/gui_test.go

+ 4 - 2
cmd/syncthing/gui.go

@@ -442,10 +442,12 @@ func corsMiddleware(next http.Handler) http.Handler {
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		// Process OPTIONS requests
 		if r.Method == "OPTIONS" {
+			// Add a generous access-control-allow-origin header for CORS requests
+			w.Header().Add("Access-Control-Allow-Origin", "*")
 			// Only GET/POST Methods are supported
 			w.Header().Set("Access-Control-Allow-Methods", "GET, POST")
-			// Only this custom header can be set
-			w.Header().Set("Access-Control-Allow-Headers", "X-API-Key")
+			// Only these headers can be set
+			w.Header().Set("Access-Control-Allow-Headers", "Content-Type, X-API-Key")
 			// The request is meant to be cached 10 minutes
 			w.Header().Set("Access-Control-Max-Age", "600")
 

+ 3 - 0
cmd/syncthing/gui_csrf.go

@@ -37,6 +37,9 @@ func csrfMiddleware(unique string, prefix string, cfg config.GUIConfiguration, n
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		// Allow requests carrying a valid API key
 		if cfg.IsValidAPIKey(r.Header.Get("X-API-Key")) {
+			// Set the access-control-allow-origin header for CORS requests
+			// since a valid API key has been provided
+			w.Header().Add("Access-Control-Allow-Origin", "*")
 			next.ServeHTTP(w, r)
 			return
 		}

+ 61 - 0
cmd/syncthing/gui_test.go

@@ -857,3 +857,64 @@ func TestAddressIsLocalhost(t *testing.T) {
 		}
 	}
 }
+
+func TestAccessControlAllowOriginHeader(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,
+	}
+
+	req, _ := http.NewRequest("GET", baseURL+"/rest/system/status", nil)
+	req.Header.Set("X-API-Key", testAPIKey)
+	resp, err := cli.Do(req)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	resp.Body.Close()
+	if resp.StatusCode != http.StatusOK {
+		t.Fatal("GET on /rest/system/status should succeed, not", resp.Status)
+	}
+	if resp.Header.Get("Access-Control-Allow-Origin") != "*" {
+		t.Fatal("GET on /rest/system/status should return a 'Access-Control-Allow-Origin: *' header")
+	}
+}
+
+func TestOptionsRequest(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,
+	}
+
+	req, _ := http.NewRequest("OPTIONS", baseURL+"/rest/system/status", nil)
+	resp, err := cli.Do(req)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	resp.Body.Close()
+	if resp.StatusCode != http.StatusNoContent {
+		t.Fatal("OPTIONS on /rest/system/status should succeed, not", resp.Status)
+	}
+	if resp.Header.Get("Access-Control-Allow-Origin") != "*" {
+		t.Fatal("OPTIONS on /rest/system/status should return a 'Access-Control-Allow-Origin: *' header")
+	}
+	if resp.Header.Get("Access-Control-Allow-Methods") != "GET, POST" {
+		t.Fatal("OPTIONS on /rest/system/status should return a 'Access-Control-Allow-Methods: GET, POST' header")
+	}
+	if resp.Header.Get("Access-Control-Allow-Headers") != "Content-Type, X-API-Key" {
+		t.Fatal("OPTIONS on /rest/system/status should return a 'Access-Control-Allow-Headers: Content-Type, X-API-KEY' header")
+	}
+}