Bug 377496 - Add a test for rate limiting the authentication prompt. r=MattN
authorJohann Hofmann <jhofmann@mozilla.com>
Sun, 03 Feb 2019 19:42:22 +0000
changeset 456593 cf32e70c234f4ce2361bb8e01a08ccd9a8ea24c6
parent 456592 d05f776eb8408238fba4a59e24c6189c45653646
child 456594 68f9119eacf574ed41522163366e23e68fbdedfa
push id35494
push useropoprus@mozilla.com
push dateMon, 04 Feb 2019 09:23:09 +0000
treeherdermozilla-central@965b28c159d6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN
bugs377496
milestone67.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 377496 - Add a test for rate limiting the authentication prompt. r=MattN Differential Revision: https://phabricator.services.mozilla.com/D18229
toolkit/components/passwordmgr/test/browser/browser.ini
toolkit/components/passwordmgr/test/browser/browser_basicAuth_rateLimit.js
--- a/toolkit/components/passwordmgr/test/browser/browser.ini
+++ b/toolkit/components/passwordmgr/test/browser/browser.ini
@@ -11,16 +11,17 @@ support-files =
   head.js
   insecure_test.html
   insecure_test_subframe.html
   multiple_forms.html
   streamConverter_content.sjs
 
 [browser_autocomplete_insecure_warning.js]
 skip-if = os == "linux" || os == "mac"  # Bug 1425879
+[browser_basicAuth_rateLimit.js]
 [browser_capture_doorhanger.js]
 skip-if = os == "linux" && debug # Bug 1334336
 support-files =
   subtst_notifications_1.html
   subtst_notifications_2.html
   subtst_notifications_2pw_0un.html
   subtst_notifications_2pw_1un_1text.html
   subtst_notifications_3.html
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/browser/browser_basicAuth_rateLimit.js
@@ -0,0 +1,118 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This tests that the basic auth dialog can not be used for DOS attacks
+// and that the protections are reset on user-initiated navigation/reload.
+
+const PROMPT_URL = "chrome://global/content/commonDialog.xul";
+function promiseAuthWindowShown() {
+  return new Promise(resolve => {
+    let listener = {
+      onOpenWindow(xulWin) {
+        let domwindow = xulWin.docShell.domWindow;
+        waitForFocus(() => {
+          is(domwindow.document.location.href, PROMPT_URL, "Should have seen a prompt window");
+          is(domwindow.args.promptType, "promptUserAndPass", "Should be an authenticate prompt");
+          domwindow.document.documentElement.cancelDialog();
+          Services.wm.removeListener(listener);
+          resolve();
+        }, domwindow);
+      },
+
+      onCloseWindow() {
+      },
+    };
+
+    Services.wm.addListener(listener);
+  });
+}
+
+add_task(async function test() {
+  await BrowserTestUtils.withNewTab("https://example.com", async function(browser) {
+    let cancelDialogLimit = Services.prefs.getIntPref("prompts.authentication_dialog_abuse_limit");
+
+    let authShown = promiseAuthWindowShown();
+    let browserLoaded = BrowserTestUtils.browserLoaded(browser);
+    BrowserTestUtils.loadURI(browser, "https://example.com/browser/toolkit/components/passwordmgr/test/browser/authenticate.sjs");
+    await authShown;
+    ok(true, "Seen dialog number 1");
+    await browserLoaded;
+    ok(true, "Loaded document number 1");
+
+    // Reload the document a bit more often than should be allowed.
+    // As long as we're in the acceptable range we should receive
+    // auth prompts, otherwise we should not receive them and the
+    // page should just load.
+    // We've already seen the dialog once, hence we start the loop at 1.
+    for (let i = 1; i < cancelDialogLimit + 2; i++) {
+      if (i < cancelDialogLimit) {
+        authShown = promiseAuthWindowShown();
+      }
+      browserLoaded = BrowserTestUtils.browserLoaded(browser);
+      ContentTask.spawn(browser, null, function() {
+        content.document.location.reload();
+      });
+      if (i < cancelDialogLimit) {
+        await authShown;
+        ok(true, `Seen dialog number ${i + 1}`);
+      }
+      await browserLoaded;
+      ok(true, `Loaded document number ${i + 1}`);
+    }
+
+    let reloadButton = document.getElementById("reload-button");
+    await TestUtils.waitForCondition(() => !reloadButton.hasAttribute("disabled"));
+
+    // Verify that we can click the reload button to reset the counter.
+    authShown = promiseAuthWindowShown();
+    browserLoaded = BrowserTestUtils.browserLoaded(browser);
+    reloadButton.click();
+    await authShown;
+    ok(true, "Seen dialog number 1");
+    await browserLoaded;
+    ok(true, "Loaded document number 1");
+
+    // Now check loading subresources with auth on the page.
+    browserLoaded = BrowserTestUtils.browserLoaded(browser);
+    BrowserTestUtils.loadURI(browser, "https://example.com");
+    await browserLoaded;
+
+    // We've already seen the dialog once, hence we start the loop at 1.
+    for (let i = 1; i < cancelDialogLimit + 2; i++) {
+      if (i < cancelDialogLimit) {
+        authShown = promiseAuthWindowShown();
+      }
+
+      let iframeLoaded = ContentTask.spawn(browser, null, async function() {
+        let doc = content.document;
+        let iframe = doc.createElement("iframe");
+        doc.body.appendChild(iframe);
+        let loaded = new Promise(resolve => {
+          iframe.addEventListener("load", function(e) {
+            resolve();
+          }, {once: true});
+        });
+        iframe.src = "https://example.com/browser/toolkit/components/passwordmgr/test/browser/authenticate.sjs";
+        await loaded;
+      });
+
+      if (i < cancelDialogLimit) {
+        await authShown;
+        ok(true, `Seen dialog number ${i + 1}`);
+      }
+
+      await iframeLoaded;
+      ok(true, `Loaded iframe number ${i + 1}`);
+    }
+
+    // Verify that pressing enter in the urlbar also resets the counter.
+    authShown = promiseAuthWindowShown();
+    browserLoaded = BrowserTestUtils.browserLoaded(browser);
+    gURLBar.value = "https://example.com/browser/toolkit/components/passwordmgr/test/browser/authenticate.sjs";
+    gURLBar.focus();
+    EventUtils.synthesizeKey("KEY_Enter");
+    await authShown;
+    await browserLoaded;
+  });
+});