Bug 1541923 - Avoid leaking cookies via Normandy fetches r=Gijs
authorMichael Cooper <mcooper@mozilla.com>
Tue, 25 Jun 2019 20:14:23 +0000
changeset 480087 caacd1cb3ec10b6d39a4fbdc5eb09a7a703d87c3
parent 480086 44a166a4efdbfd3270f1e91c854f0dba4e62ddfb
child 480088 308ea5eaadf1401186e31428f4528120b9803235
push id36201
push usercsabou@mozilla.com
push dateWed, 26 Jun 2019 03:57:29 +0000
treeherdermozilla-central@a3cad1d7836c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs
bugs1541923
milestone69.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1541923 - Avoid leaking cookies via Normandy fetches r=Gijs Differential Revision: https://phabricator.services.mozilla.com/D31810
toolkit/components/normandy/lib/NormandyApi.jsm
toolkit/components/normandy/test/unit/cookie_server.sjs
toolkit/components/normandy/test/unit/test_NormandyApi.js
toolkit/components/normandy/test/unit/xpcshell.ini
--- a/toolkit/components/normandy/lib/NormandyApi.jsm
+++ b/toolkit/components/normandy/lib/NormandyApi.jsm
@@ -38,17 +38,17 @@ var NormandyApi = {
           url.searchParams.set(key, data[key]);
         }
       } else if (method === "post") {
         body = JSON.stringify(data);
       }
     }
 
     const headers = {"Accept": "application/json"};
-    return fetch(url.href, {method, body, headers});
+    return fetch(url.href, {method, body, headers, credentials: "omit"});
   },
 
   get(endpoint, data) {
     return this.apiCall("get", endpoint, data);
   },
 
   post(endpoint, data) {
     return this.apiCall("post", endpoint, data);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/normandy/test/unit/cookie_server.sjs
@@ -0,0 +1,12 @@
+/**
+ * Sends responses that sets a cookie.
+ */
+function handleRequest(request, response) {
+  // Allow cross-origin, so you can XHR to it!
+  response.setHeader("Access-Control-Allow-Origin", "*", false);
+  // Avoid confusing cache behaviors
+  response.setHeader("Cache-Control", "no-cache", false);
+  // Set a cookie
+  response.setHeader("Set-Cookie", "type=chocolate-chip", false);
+  response.write("");
+}
--- a/toolkit/components/normandy/test/unit/test_NormandyApi.js
+++ b/toolkit/components/normandy/test/unit/test_NormandyApi.js
@@ -1,14 +1,17 @@
 /* globals sinon */
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/CanonicalJSON.jsm", this);
 ChromeUtils.import("resource://gre/modules/osfile.jsm", this);
 ChromeUtils.import("resource://normandy/lib/NormandyApi.jsm", this);
+ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm", this);
+
+Cu.importGlobalProperties(["fetch"]);
 
 load("utils.js"); /* globals withMockApiServer, MockResponse, withScriptServer, withServer, makeMockApiServer */
 
 add_task(withMockApiServer(async function test_get(serverUrl) {
   // Test that NormandyApi can fetch from the test server.
   const response = await NormandyApi.get(`${serverUrl}/api/v1/`);
   const data = await response.json();
   equal(data["recipe-signed"], "/api/v1/recipe/signed/", "Expected data in response");
@@ -168,8 +171,47 @@ add_task(withScriptServer("query_server.
   // Test that NormandyApi can POST JSON-formatted data to the test server.
   const response = await NormandyApi.post(serverUrl, {foo: "bar", baz: "biff"});
   const data = await response.json();
   Assert.deepEqual(
     data, {queryString: {}, body: {foo: "bar", baz: "biff"}},
     "NormandyApi sent an incorrect query string."
   );
 }));
+
+// Test that no credentials are sent, even if the cookie store contains them.
+add_task(withScriptServer("cookie_server.sjs", async function test_sendsNoCredentials(serverUrl) {
+  // This test uses cookie_server.sjs, which responds to all requests with a
+  // response that sets a cookie.
+
+  // send a request, to store a cookie in the cookie store
+  await fetch(serverUrl);
+
+  // A normal request should send that cookie
+  const cookieExpectedDeferred = PromiseUtils.defer();
+  function cookieExpectedObserver(aSubject, aTopic, aData) {
+    equal(aTopic, "http-on-modify-request", "Only the expected topic should be observed");
+    let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+    equal(httpChannel.getRequestHeader("Cookie"), "type=chocolate-chip", "The header should be sent");
+    Services.obs.removeObserver(cookieExpectedObserver, "http-on-modify-request");
+    cookieExpectedDeferred.resolve();
+  }
+  Services.obs.addObserver(cookieExpectedObserver, "http-on-modify-request");
+  await fetch(serverUrl);
+  await cookieExpectedDeferred.promise;
+
+  // A request through the NormandyApi method should not send that cookie
+  const cookieNotExpectedDeferred = PromiseUtils.defer();
+  function cookieNotExpectedObserver(aSubject, aTopic, aData) {
+    equal(aTopic, "http-on-modify-request", "Only the expected topic should be observed");
+    let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+    Assert.throws(
+      () => httpChannel.getRequestHeader("Cookie"),
+      /NS_ERROR_NOT_AVAILABLE/,
+      "The cookie header should not be sent"
+    );
+    Services.obs.removeObserver(cookieNotExpectedObserver, "http-on-modify-request");
+    cookieNotExpectedDeferred.resolve();
+  }
+  Services.obs.addObserver(cookieNotExpectedObserver, "http-on-modify-request");
+  await NormandyApi.get(serverUrl);
+  await cookieNotExpectedDeferred.promise;
+}));
--- a/toolkit/components/normandy/test/unit/xpcshell.ini
+++ b/toolkit/components/normandy/test/unit/xpcshell.ini
@@ -1,13 +1,14 @@
 [DEFAULT]
 head = head_xpc.js
 firefox-appdir = browser
 support-files =
   mock_api/**
   invalid_recipe_signature_api/**
   query_server.sjs
   echo_server.sjs
+  cookie_server.sjs
   utils.js
 tags = normandy
 
 [test_addon_unenroll.js]
 [test_NormandyApi.js]