summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/test/xpcshell/test_ext_same_site_redirects.js
blob: f42a74e0bb2d6a487a461a97452d52c2d1bca39a (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
"use strict";

/**
 * This test tests various redirection scenarios, and checks whether sameSite
 * cookies are sent.
 *
 * The file has the following tests:
 * - verify_firstparty_web_behavior - base case, confirms normal web behavior.
 * - samesite_is_foreign_without_host_permissions
 * - wildcard_host_permissions_enable_samesite_cookies
 * - explicit_host_permissions_enable_samesite_cookies
 * - some_host_permissions_enable_some_samesite_cookies
 */

// This simulates a common pattern used for sites that require authentication.
// After logging in, there may be multiple redirects, HTTP and scripted.
const SITE_START = "start.example.net";
// set "start" cookies + 302 redirects to found.
const SITE_FOUND = "found.example.net";
// set "found" cookies + uses a HTML redirect to redir.
const SITE_REDIR = "redir.example.net";
// set "redir" cookies + 302 redirects to final.
const SITE_FINAL = "final.example.net";

const SITE = "example.net";

const URL_START = `http://${SITE_START}/start`;

const server = createHttpServer({
  hosts: [SITE_START, SITE_FOUND, SITE_REDIR, SITE_FINAL],
});

function getCookies(request) {
  return request.hasHeader("Cookie") ? request.getHeader("Cookie") : "";
}

function sendCookies(response, prefix, suffix = "") {
  const cookies = [
    prefix + "-none=1; sameSite=none; domain=" + SITE + suffix,
    prefix + "-lax=1; sameSite=lax; domain=" + SITE + suffix,
    prefix + "-strict=1; sameSite=strict; domain=" + SITE + suffix,
  ];
  for (let cookie of cookies) {
    response.setHeader("Set-Cookie", cookie, true);
  }
}

function deleteCookies(response, prefix) {
  sendCookies(response, prefix, "; expires=Thu, 01 Jan 1970 00:00:00 GMT");
}

var receivedCookies = [];

server.registerPathHandler("/start", (request, response) => {
  Assert.equal(request.host, SITE_START);
  Assert.equal(getCookies(request), "", "No cookies at start of test");

  response.setStatusLine(request.httpVersion, 302, "Found");
  sendCookies(response, "start");
  response.setHeader("Location", `http://${SITE_FOUND}/found`);
});

server.registerPathHandler("/found", (request, response) => {
  Assert.equal(request.host, SITE_FOUND);
  receivedCookies.push(getCookies(request));

  response.setHeader("Content-Type", "text/html; charset=utf-8", false);
  deleteCookies(response, "start");
  sendCookies(response, "found");
  response.write(`<script>location = "http://${SITE_REDIR}/redir";</script>`);
});

server.registerPathHandler("/redir", (request, response) => {
  Assert.equal(request.host, SITE_REDIR);
  receivedCookies.push(getCookies(request));

  response.setStatusLine(request.httpVersion, 302, "Found");
  deleteCookies(response, "found");
  sendCookies(response, "redir");
  response.setHeader("Location", `http://${SITE_FINAL}/final`);
});

server.registerPathHandler("/final", (request, response) => {
  Assert.equal(request.host, SITE_FINAL);
  receivedCookies.push(getCookies(request));

  response.setStatusLine(request.httpVersion, 302, "Found");
  deleteCookies(response, "redir");
  // In test some_host_permissions_enable_some_samesite_cookies, the cookies
  // from the start haven't been cleared due to the lack of host permissions.
  // Do that here instead.
  deleteCookies(response, "start");
  response.setHeader("Location", "/final_and_clean");
});

// Should be called before any request is made.
function promiseFinalResponse() {
  Assert.deepEqual(receivedCookies, [], "Test starts without observed cookies");
  return new Promise(resolve => {
    server.registerPathHandler("/final_and_clean", request => {
      Assert.equal(request.host, SITE_FINAL);
      Assert.equal(getCookies(request), "", "Cookies cleaned up");
      resolve(receivedCookies.splice(0));
    });
  });
}

// Load the page as a child frame of an extension, for the given permissions.
async function getCookiesForLoadInExtension({ permissions }) {
  // embedder.html loads http:-frame.
  allow_unsafe_parent_loads_when_extensions_not_remote();

  let extension = ExtensionTestUtils.loadExtension({
    manifest: {
      permissions,
    },
    files: {
      "embedder.html": `<iframe src="${URL_START}"></iframe>`,
    },
  });
  await extension.startup();
  let cookiesPromise = promiseFinalResponse();
  let contentPage = await ExtensionTestUtils.loadContentPage(
    `moz-extension://${extension.uuid}/embedder.html`,
    { extension }
  );
  let cookies = await cookiesPromise;
  await contentPage.close();
  await extension.unload();

  revert_allow_unsafe_parent_loads_when_extensions_not_remote();

  return cookies;
}

add_task(async function setup() {
  Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
  Services.prefs.setBoolPref("network.cookie.sameSite.laxByDefault", true);

  // Test server runs on http, so disable Secure requirement of sameSite=none.
  Services.prefs.setBoolPref(
    "network.cookie.sameSite.noneRequiresSecure",
    false
  );
});

// First verify that our expectations match with the actual behavior on the web.
add_task(async function verify_firstparty_web_behavior() {
  let cookiesPromise = promiseFinalResponse();
  let contentPage = await ExtensionTestUtils.loadContentPage(URL_START);
  let cookies = await cookiesPromise;
  await contentPage.close();
  Assert.deepEqual(
    cookies,
    // Same expectations as in host_permissions_enable_samesite_cookies
    [
      "start-none=1; start-lax=1; start-strict=1",
      "found-none=1; found-lax=1; found-strict=1",
      "redir-none=1; redir-lax=1; redir-strict=1",
    ],
    "Expected cookies from a first-party load on the web"
  );
});

// Verify that an extension without permission behaves like a third-party page.
add_task(async function samesite_is_foreign_without_host_permissions() {
  let cookies = await getCookiesForLoadInExtension({
    permissions: [],
  });

  Assert.deepEqual(
    cookies,
    ["start-none=1", "found-none=1", "redir-none=1"],
    "SameSite cookies excluded without permissions"
  );
});

// When an extension has permissions for the site, cookies should be included.
add_task(async function wildcard_host_permissions_enable_samesite_cookies() {
  let cookies = await getCookiesForLoadInExtension({
    permissions: ["*://*.example.net/*"], // = *.SITE
  });

  Assert.deepEqual(
    cookies,
    // Same expectations as in verify_firstparty_web_behavior.
    [
      "start-none=1; start-lax=1; start-strict=1",
      "found-none=1; found-lax=1; found-strict=1",
      "redir-none=1; redir-lax=1; redir-strict=1",
    ],
    "Expected cookies from a load in an extension frame"
  );
});

// When an extension has permissions for the site, cookies should be included.
add_task(async function explicit_host_permissions_enable_samesite_cookies() {
  let cookies = await getCookiesForLoadInExtension({
    permissions: [
      "*://start.example.net/*",
      "*://found.example.net/*",
      "*://redir.example.net/*",
      "*://final.example.net/*",
    ],
  });

  Assert.deepEqual(
    cookies,
    // Same expectations as in verify_firstparty_web_behavior.
    [
      "start-none=1; start-lax=1; start-strict=1",
      "found-none=1; found-lax=1; found-strict=1",
      "redir-none=1; redir-lax=1; redir-strict=1",
    ],
    "Expected cookies from a load in an extension frame"
  );
});

// When an extension does not have host permissions for all sites, but only
// some, then same-site cookies are only included in requests with the right
// permissions.
add_task(async function some_host_permissions_enable_some_samesite_cookies() {
  let cookies = await getCookiesForLoadInExtension({
    permissions: ["*://start.example.net/*", "*://final.example.net/*"],
  });

  Assert.deepEqual(
    cookies,
    [
      // Missing permission for "found.example.net":
      "start-none=1",
      // Missing permission for "redir.example.net":
      "found-none=1",
      // "final.example.net" can see cookies from "start.example.net":
      "start-lax=1; start-strict=1; redir-none=1",
    ],
    "Expected some cookies from a load in an extension frame"
  );
});