summaryrefslogtreecommitdiffstats
path: root/caps/tests/unit/test_precursor_principal.js
blob: 37c1ae13bcdeab4ceab45de8685f9d5085e6bb0a (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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
"use strict";
const { TestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/TestUtils.sys.mjs"
);
const { XPCShellContentUtils } = ChromeUtils.importESModule(
  "resource://testing-common/XPCShellContentUtils.sys.mjs"
);
const { ExtensionTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/ExtensionXPCShellUtils.sys.mjs"
);

XPCShellContentUtils.init(this);
ExtensionTestUtils.init(this);

const server = XPCShellContentUtils.createHttpServer({
  hosts: ["example.com", "example.org"],
});

server.registerPathHandler("/static_frames", (request, response) => {
  response.setHeader("Content-Type", "text/html");
  response.write(`
    <iframe name="same_origin" sandbox="allow-scripts allow-same-origin" src="http://example.com/frame"></iframe>
    <iframe name="same_origin_sandbox" sandbox="allow-scripts" src="http://example.com/frame"></iframe>
    <iframe name="cross_origin" sandbox="allow-scripts allow-same-origin" src="http://example.org/frame"></iframe>
    <iframe name="cross_origin_sandbox" sandbox="allow-scripts" src="http://example.org/frame"></iframe>
    <iframe name="data_uri" sandbox="allow-scripts allow-same-origin" src="data:text/html,<h1>Data Subframe</h1>"></iframe>
    <iframe name="data_uri_sandbox" sandbox="allow-scripts" src="data:text/html,<h1>Data Subframe</h1>"></iframe>
    <iframe name="srcdoc" sandbox="allow-scripts allow-same-origin" srcdoc="<h1>Srcdoc Subframe</h1>"></iframe>
    <iframe name="srcdoc_sandbox" sandbox="allow-scripts" srcdoc="<h1>Srcdoc Subframe</h1>"></iframe>
    <iframe name="blank" sandbox="allow-scripts allow-same-origin"></iframe>
    <iframe name="blank_sandbox" sandbox="allow-scripts"></iframe>
    <iframe name="redirect_com" sandbox="allow-scripts allow-same-origin" src="/redirect?com"></iframe>
    <iframe name="redirect_com_sandbox" sandbox="allow-scripts" src="/redirect?com"></iframe>
    <iframe name="redirect_org" sandbox="allow-scripts allow-same-origin" src="/redirect?org"></iframe>
    <iframe name="redirect_org_sandbox" sandbox="allow-scripts" src="/redirect?org"></iframe>
    <iframe name="ext_redirect_com_com" sandbox="allow-scripts allow-same-origin" src="/ext_redirect?com"></iframe>
    <iframe name="ext_redirect_com_com_sandbox" sandbox="allow-scripts" src="/ext_redirect?com"></iframe>
    <iframe name="ext_redirect_org_com" sandbox="allow-scripts allow-same-origin" src="http://example.org/ext_redirect?com"></iframe>
    <iframe name="ext_redirect_org_com_sandbox" sandbox="allow-scripts" src="http://example.org/ext_redirect?com"></iframe>
    <iframe name="ext_redirect_com_org" sandbox="allow-scripts allow-same-origin" src="/ext_redirect?org"></iframe>
    <iframe name="ext_redirect_com_org_sandbox" sandbox="allow-scripts" src="/ext_redirect?org"></iframe>
    <iframe name="ext_redirect_org_org" sandbox="allow-scripts allow-same-origin" src="http://example.org/ext_redirect?org"></iframe>
    <iframe name="ext_redirect_org_org_sandbox" sandbox="allow-scripts" src="http://example.org/ext_redirect?org"></iframe>
    <iframe name="ext_redirect_com_data" sandbox="allow-scripts allow-same-origin" src="/ext_redirect?data"></iframe>
    <iframe name="ext_redirect_com_data_sandbox" sandbox="allow-scripts" src="/ext_redirect?data"></iframe>
    <iframe name="ext_redirect_org_data" sandbox="allow-scripts allow-same-origin" src="http://example.org/ext_redirect?data"></iframe>
    <iframe name="ext_redirect_org_data_sandbox" sandbox="allow-scripts" src="http://example.org/ext_redirect?data"></iframe>

    <!-- XXX(nika): These aren't static as they perform loads dynamically - perhaps consider testing them separately? -->
    <iframe name="client_replace_org_blank" sandbox="allow-scripts allow-same-origin" src="http://example.org/client_replace?blank"></iframe>
    <iframe name="client_replace_org_blank_sandbox" sandbox="allow-scripts" src="http://example.org/client_replace?blank"></iframe>
    <iframe name="client_replace_org_data" sandbox="allow-scripts allow-same-origin" src="http://example.org/client_replace?data"></iframe>
    <iframe name="client_replace_org_data_sandbox" sandbox="allow-scripts" src="http://example.org/client_replace?data"></iframe>
  `);
});

server.registerPathHandler("/frame", (request, response) => {
  response.setHeader("Content-Type", "text/html");
  response.write(`<h1>HTTP Subframe</h1>`);
});

server.registerPathHandler("/redirect", (request, response) => {
  let redirect;
  if (request.queryString == "com") {
    redirect = "http://example.com/frame";
  } else if (request.queryString == "org") {
    redirect = "http://example.org/frame";
  } else {
    response.setStatusLine(request.httpVersion, 404, "Not found");
    return;
  }

  response.setStatusLine(request.httpVersion, 302, "Found");
  response.setHeader("Location", redirect);
});

server.registerPathHandler("/client_replace", (request, response) => {
  let redirect;
  if (request.queryString == "blank") {
    redirect = "about:blank";
  } else if (request.queryString == "data") {
    redirect = "data:text/html,<h1>Data Subframe</h1>";
  } else {
    response.setStatusLine(request.httpVersion, 404, "Not found");
    return;
  }

  response.setHeader("Content-Type", "text/html");
  response.write(`
    <script>
      window.location.replace(${JSON.stringify(redirect)});
    </script>
  `);
});

add_task(async function sandboxed_precursor() {
  // Bug 1725345: Make XPCShellContentUtils.createHttpServer support https
  Services.prefs.setBoolPref("dom.security.https_first", false);

  let extension = await ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["webRequest", "webRequestBlocking", "<all_urls>"],
    },
    background() {
      // eslint-disable-next-line no-undef
      browser.webRequest.onBeforeRequest.addListener(
        details => {
          let url = new URL(details.url);
          if (!url.pathname.includes("ext_redirect")) {
            return {};
          }

          let redirectUrl;
          if (url.search == "?com") {
            redirectUrl = "http://example.com/frame";
          } else if (url.search == "?org") {
            redirectUrl = "http://example.org/frame";
          } else if (url.search == "?data") {
            redirectUrl = "data:text/html,<h1>Data Subframe</h1>";
          }
          return { redirectUrl };
        },
        { urls: ["<all_urls>"] },
        ["blocking"]
      );
    },
  });
  await extension.startup();

  registerCleanupFunction(async function () {
    await extension.unload();
  });

  for (let userContextId of [undefined, 1]) {
    let comURI = Services.io.newURI("http://example.com");
    let comPrin = Services.scriptSecurityManager.createContentPrincipal(
      comURI,
      { userContextId }
    );
    let orgURI = Services.io.newURI("http://example.org");
    let orgPrin = Services.scriptSecurityManager.createContentPrincipal(
      orgURI,
      { userContextId }
    );

    let page = await XPCShellContentUtils.loadContentPage(
      "http://example.com/static_frames",
      {
        remote: true,
        remoteSubframes: true,
        userContextId,
      }
    );
    let bc = page.browsingContext;

    ok(
      bc.currentWindowGlobal.documentPrincipal.equals(comPrin),
      "toplevel principal matches"
    );

    // XXX: This is sketchy as heck, but it's also the easiest way to wait for
    // the `window.location.replace` loads to finish.
    await TestUtils.waitForCondition(
      () =>
        bc.children.every(
          child =>
            !child.currentWindowGlobal.documentURI.spec.includes(
              "/client_replace"
            )
        ),
      "wait for every client_replace global to be replaced"
    );

    let principals = {};
    for (let child of bc.children) {
      notEqual(child.name, "", "child frames must have names");
      ok(!(child.name in principals), "duplicate child frame name");
      principals[child.name] = child.currentWindowGlobal.documentPrincipal;
    }

    function principal_is(name, expected) {
      let principal = principals[name];
      info(`${name} = ${principal.origin}`);
      ok(principal.equals(expected), `${name} is correct`);
    }
    function precursor_is(name, precursor) {
      let principal = principals[name];
      info(`${name} = ${principal.origin}`);
      ok(principal.isNullPrincipal, `${name} is null`);
      ok(
        principal.precursorPrincipal.equals(precursor),
        `${name} has the correct precursor`
      );
    }

    // Basic loads should have the principals or precursor principals for the
    // document being loaded.
    principal_is("same_origin", comPrin);
    precursor_is("same_origin_sandbox", comPrin);

    principal_is("cross_origin", orgPrin);
    precursor_is("cross_origin_sandbox", orgPrin);

    // Loads of a data: URI should complete with a sandboxed principal based on
    // the principal which tried to perform the load.
    precursor_is("data_uri", comPrin);
    precursor_is("data_uri_sandbox", comPrin);

    // Loads which inherit principals, such as srcdoc an about:blank loads,
    // should also inherit sandboxed precursor principals.
    principal_is("srcdoc", comPrin);
    precursor_is("srcdoc_sandbox", comPrin);

    principal_is("blank", comPrin);
    precursor_is("blank_sandbox", comPrin);

    // Redirects shouldn't interfere with the final principal, and it should be
    // based only on the final URI.
    principal_is("redirect_com", comPrin);
    precursor_is("redirect_com_sandbox", comPrin);

    principal_is("redirect_org", orgPrin);
    precursor_is("redirect_org_sandbox", orgPrin);

    // Extension redirects should act like normal redirects, and still resolve
    // with the principal or sandboxed principal of the final URI.
    principal_is("ext_redirect_com_com", comPrin);
    precursor_is("ext_redirect_com_com_sandbox", comPrin);

    principal_is("ext_redirect_com_org", orgPrin);
    precursor_is("ext_redirect_com_org_sandbox", orgPrin);

    principal_is("ext_redirect_org_com", comPrin);
    precursor_is("ext_redirect_org_com_sandbox", comPrin);

    principal_is("ext_redirect_org_org", orgPrin);
    precursor_is("ext_redirect_org_org_sandbox", orgPrin);

    // When an extension redirects to a data: URI, we use the last non-data: URI
    // in the chain as the precursor principal.
    // FIXME: This should perhaps use the extension's principal instead?
    precursor_is("ext_redirect_com_data", comPrin);
    precursor_is("ext_redirect_com_data_sandbox", comPrin);

    precursor_is("ext_redirect_org_data", orgPrin);
    precursor_is("ext_redirect_org_data_sandbox", orgPrin);

    // Check that navigations triggred by script within the frames will have the
    // correct behaviour when navigating to blank and data URIs.
    principal_is("client_replace_org_blank", orgPrin);
    precursor_is("client_replace_org_blank_sandbox", orgPrin);

    precursor_is("client_replace_org_data", orgPrin);
    precursor_is("client_replace_org_data_sandbox", orgPrin);

    await page.close();
  }
  Services.prefs.clearUserPref("dom.security.https_first");
});