summaryrefslogtreecommitdiffstats
path: root/toolkit/components/passwordmgr/test/browser/browser_basicAuth_multiTab.js
blob: 2115272abef513f8fef18e2111ced949e9ec6acd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/* 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/. */

"use strict";

/**
 * Tests that can show multiple auth prompts in different tabs and handle them
 * correctly.
 */

const ORIGIN1 = "https://example.com";
const ORIGIN2 = "https://example.org";

const AUTH_PATH =
  "/browser/toolkit/components/passwordmgr/test/browser/authenticate.sjs";

/**
 * Opens a tab and navigates to the auth test path.
 * @param {String} origin - Origin to open with test path.
 * @param {Object} authOptions - Authentication options to pass to server and
 * test for.
 * @param {String} authOptions.user - Expected username.
 * @param {String} authOptions.pass - Expected password.
 * @param {String} authOptions.realm - Realm to return on auth request.
 * @returns {Object} - An object containing passed origin and authOptions,
 * opened tab, a promise which resolves once the tab loads, a promise which
 * resolves once the prompt has been opened.
 */
async function openTabWithAuthPrompt(origin, authOptions) {
  let tab = await BrowserTestUtils.openNewForegroundTab(
    gBrowser,
    "https://example.com"
  );

  let promptPromise = PromptTestUtils.waitForPrompt(tab.linkedBrowser, {
    modalType: Services.prompt.MODAL_TYPE_TAB,
    promptType: "promptUserAndPass",
  });
  let url = new URL(origin + AUTH_PATH);
  Object.entries(authOptions).forEach(([key, value]) =>
    url.searchParams.append(key, value)
  );
  let loadPromise = BrowserTestUtils.browserLoaded(
    tab.linkedBrowser,
    false,
    url.toString()
  );
  info("Loading " + url.toString());
  BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, url.toString());
  return { origin, tab, authOptions, loadPromise, promptPromise };
}

/**
 * Waits for tab to load and tests for expected auth state.
 * @param {boolean} expectAuthed - true: auth success, false otherwise.
 * @param {Object} tabInfo - Information about the tab as generated by
 * openTabWithAuthPrompt.
 */
async function testTabAuthed(expectAuthed, { tab, loadPromise, authOptions }) {
  // Wait for tab to load after auth.
  await loadPromise;
  Assert.ok(true, "Tab loads after auth");

  // Fetch auth results from body (set by authenticate.sjs).
  let { loginResult, user, pass } = await SpecialPowers.spawn(
    tab.linkedBrowser,
    [],
    () => {
      let result = {};
      result.loginResult = content.document.getElementById("ok").innerText;
      result.user = content.document.getElementById("user").innerText;
      result.pass = content.document.getElementById("pass").innerText;
      return result;
    }
  );

  Assert.equal(
    loginResult == "PASS",
    expectAuthed,
    "Site has expected auth state"
  );
  Assert.equal(user, expectAuthed ? authOptions.user : "", "Sent correct user");
  Assert.equal(
    pass,
    expectAuthed ? authOptions.pass : "",
    "Sent correct password"
  );
}

add_task(async function test() {
  let tabA = await openTabWithAuthPrompt(ORIGIN1, {
    user: "userA",
    pass: "passA",
    realm: "realmA",
  });
  // Tab B and C share realm and credentials.
  // However, since the auth happens in separate tabs we should get two prompts.
  let tabB = await openTabWithAuthPrompt(ORIGIN2, {
    user: "userB",
    pass: "passB",
    realm: "realmB",
  });
  let tabC = await openTabWithAuthPrompt(ORIGIN2, {
    user: "userB",
    pass: "passB",
    realm: "realmB",
  });
  let tabs = [tabA, tabB, tabC];

  info(`Opening ${tabs.length} tabs with auth prompts`);
  let prompts = await Promise.all(tabs.map(tab => tab.promptPromise));

  Assert.equal(prompts.length, tabs.length, "Should have one prompt per tab");

  for (let i = 0; i < prompts.length; i++) {
    let titleEl = prompts[i].ui.prompt.document.querySelector("#titleText");
    Assert.equal(
      titleEl.textContent,
      new URL(tabs[i].origin).host,
      "Prompt matches the tab's host"
    );
  }

  // Interact with the prompts. This is deliberately done out of order
  // (no FIFO, LIFO).
  let [promptA, promptB, promptC] = prompts;

  // Accept prompt B with correct login details.
  await PromptTestUtils.handlePrompt(promptB, {
    loginInput: tabB.authOptions.user,
    passwordInput: tabB.authOptions.pass,
  });
  await testTabAuthed(true, tabB);

  // Accept prompt A with correct login details
  await PromptTestUtils.handlePrompt(promptA, {
    loginInput: tabA.authOptions.user,
    passwordInput: tabA.authOptions.pass,
  });
  await testTabAuthed(true, tabA);

  // Cancel prompt C
  await PromptTestUtils.handlePrompt(promptC, {
    buttonNumClick: 1,
  });
  await testTabAuthed(false, tabC);

  // Cleanup tabs
  tabs.forEach(({ tab }) => BrowserTestUtils.removeTab(tab));
});