Bug 1406675 - Allow storages in WebExtensions on customized cookieBehavior and lifetimePolicy prefs. r=asuth,aswan

This commit ensures that WebExtension principals always get a nsICookieService::BEHAVIOR_ACCEPT
cookieBehavior and a nsICookieService::ACCEPT_NORMALLY aLifetimePolicy:

- the webextension pages are still able to use indexedDB and localStorage on a globally
  configured:

  "network.cookie.cookieBehavior = 2"

  ("Accept cookies from websites" unchecked in the about:preferences
  "use custom settings for history" section)

- the webextension pages' localStorage does not switch in session-only mode on a globally
  configured:

    "network.cookie.lifetimePolicy = 2"

  ("Keep until I close Firefox" in the about:preferences
  "use custom settings for history" section)


MozReview-Commit-ID: 5LOCvCgcokM

--HG--
extra : rebase_source : 419dfdf953d7d49c0a3893da3242b43904155597
This commit is contained in:
Luca Greco 2017-11-10 00:49:33 +01:00
Родитель 1155b62924
Коммит edaec7544b
3 изменённых файлов: 228 добавлений и 1 удалений

Просмотреть файл

@ -9138,7 +9138,17 @@ nsContentUtils::InternalStorageAllowedForPrincipal(nsIPrincipal* aPrincipal,
uint32_t lifetimePolicy;
uint32_t behavior;
GetCookieBehaviorForPrincipal(aPrincipal, &lifetimePolicy, &behavior);
// WebExtensions principals always get BEHAVIOR_ACCEPT as cookieBehavior
// and ACCEPT_NORMALLY as lifetimePolicy (See Bug 1406675 for rationale).
auto policy = BasePrincipal::Cast(aPrincipal)->AddonPolicy();
if (policy) {
behavior = nsICookieService::BEHAVIOR_ACCEPT;
lifetimePolicy = nsICookieService::ACCEPT_NORMALLY;
} else {
GetCookieBehaviorForPrincipal(aPrincipal, &lifetimePolicy, &behavior);
}
// Check if we should only allow storage for the session, and record that fact
if (lifetimePolicy == nsICookieService::ACCEPT_SESSION) {

Просмотреть файл

@ -0,0 +1,216 @@
"use strict";
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/ExtensionParent.jsm");
const {
// cookieBehavior constants.
BEHAVIOR_REJECT_FOREIGN,
BEHAVIOR_REJECT,
BEHAVIOR_LIMIT_FOREIGN,
// lifetimePolicy constants.
ACCEPT_SESSION,
} = Ci.nsICookieService;
const server = createHttpServer();
server.registerDirectory("/data/", do_get_file("data"));
const BASE_URL = `http://localhost:${server.identity.primaryPort}/data`;
// Test that the indexedDB and localStorage are allowed in an extension background page
// and that the indexedDB is allowed in a extension worker.
async function test_bg_page_allowed_storage() {
function background() {
try {
void indexedDB;
void localStorage;
const worker = new Worker("worker.js");
worker.onmessage = (event) => {
if (event.data.pass) {
browser.test.notifyPass("bg_allowed_storage");
} else {
browser.test.notifyFail("bg_allowed_storage");
}
};
worker.postMessage({});
} catch (err) {
browser.test.notifyFail("bg_allowed_storage");
throw err;
}
}
let extension = ExtensionTestUtils.loadExtension({
background,
files: {
"worker.js": function worker() {
this.onmessage = () => {
try {
void indexedDB;
postMessage({pass: true});
} catch (err) {
postMessage({pass: false});
throw err;
}
};
},
},
});
await extension.startup();
await extension.awaitFinish("bg_allowed_storage");
await extension.unload();
}
add_task(async function test_ext_page_allowed_storage_on_cookieBehaviors() {
do_print("Test background page indexedDB with BEHAVIOR_LIMIT_FOREIGN");
Services.prefs.setIntPref("network.cookie.cookieBehavior", BEHAVIOR_LIMIT_FOREIGN);
await test_bg_page_allowed_storage();
do_print("Test background page indexedDB with BEHAVIOR_REJECT_FOREIGN");
Services.prefs.setIntPref("network.cookie.cookieBehavior", BEHAVIOR_REJECT_FOREIGN);
await test_bg_page_allowed_storage();
do_print("Test background page indexedDB with BEHAVIOR_REJECT");
Services.prefs.setIntPref("network.cookie.cookieBehavior", BEHAVIOR_REJECT);
await test_bg_page_allowed_storage();
});
// Test that the webpage's indexedDB and localStorage are still not allowed from a content script
// when the cookie behavior, even when they are allowed in the extension pages.
add_task(async function test_content_script_on_cookieBehaviorReject() {
Services.prefs.setIntPref("network.cookie.cookieBehavior", BEHAVIOR_REJECT);
function contentScript() {
// Ensure that when the current cookieBehavior doesn't allow a webpage to use indexedDB
// or localStorage, then a WebExtension content script is not allowed to use it as well.
browser.test.assertThrows(
() => indexedDB,
/The operation is insecure/,
"a content script can't use indexedDB from a page where it is disallowed"
);
browser.test.assertThrows(
() => localStorage,
/The operation is insecure/,
"a content script can't use localStorage from a page where it is disallowed"
);
browser.test.notifyPass("cs_disallowed_storage");
}
let extension = ExtensionTestUtils.loadExtension({
manifest: {
content_scripts: [{
matches: ["http://*/*/file_sample.html"],
js: ["content_script.js"],
}],
},
files: {
"content_script.js": contentScript,
},
});
await extension.startup();
let contentPage = await ExtensionTestUtils.loadContentPage(`${BASE_URL}/file_sample.html`);
await extension.awaitFinish("cs_disallowed_storage");
await contentPage.close();
await extension.unload();
});
add_task(function clear_cookieBehavior_pref() {
Services.prefs.clearUserPref("network.cookie.cookieBehavior");
});
// Test that localStorage is not in session-only mode for the extension pages,
// even when the session-only mode has been globally enabled.
add_task(async function test_localStorage_on_session_lifetimePolicy() {
// localStorage in session-only mode.
Services.prefs.setIntPref("network.cookie.lifetimePolicy", ACCEPT_SESSION);
function background() {
localStorage.setItem("test-key", "test-value");
browser.test.sendMessage("bg_localStorage_set", {uuid: window.location.hostname});
}
let extension = ExtensionTestUtils.loadExtension({
background,
});
await extension.startup();
const {uuid} = await extension.awaitMessage("bg_localStorage_set");
const fakeAddonActor = {addonId: extension.id};
const addonBrowser = await ExtensionParent.DebugUtils.getExtensionProcessBrowser(fakeAddonActor);
const {isRemoteBrowser} = addonBrowser;
const {
isSessionOnly,
domStorageLength,
domStorageStoredValue,
} = await ContentTask.spawn(addonBrowser, {uuid, isRemoteBrowser}, (params) => {
const {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
let windowEnumerator = Services.ww.getWindowEnumerator();
let bgPageWindow;
// Search the background page window in the process where the extension is running.
while (windowEnumerator.hasMoreElements()) {
let win = windowEnumerator.getNext();
// When running in remote-webextension mode the window enumerator
// will only include top level windows related to the extension process
// (the background page and the "about:blank" related to the addonBrowser
// used to connect to the right process).
if (!params.isRemoteBrowser) {
if (win.location.href !== "chrome://extensions/content/dummy.xul") {
// When running in single process mode, all the top level windows
// will be enumerated, we ignore any window that is not an
// extension windowlessBrowser.
continue;
} else {
// from an extension windowlessBrowser, retrieve the background page window
// (which has been loaded inside a XUL browser element).
win = win.document.querySelector("browser").contentWindow;
}
}
if (win.location.hostname === params.uuid &&
win.location.pathname === "/_generated_background_page.html") {
// Once we have found the background page window related to the target extension
// we can exit the while loop.
bgPageWindow = win;
break;
}
}
if (!bgPageWindow) {
throw new Error("Unable to find the extension background page");
}
return {
isSessionOnly: bgPageWindow.localStorage.isSessionOnly,
domStorageLength: bgPageWindow.localStorage.length,
domStorageStoredValue: bgPageWindow.localStorage.getItem("test-key"),
};
});
await ExtensionParent.DebugUtils.releaseExtensionProcessBrowser(fakeAddonActor);
equal(isSessionOnly, false, "the extension localStorage is not set in session-only mode");
equal(domStorageLength, 1, "the extension storage contains the expected number of keys");
equal(domStorageStoredValue, "test-value", "the extension storage contains the expected data");
await extension.unload();
});
add_task(function clear_lifetimePolicy_pref() {
Services.prefs.clearUserPref("network.cookie.lifetimePolicy");
});

Просмотреть файл

@ -16,6 +16,7 @@ skip-if = os == "android"
[test_ext_browserSettings.js]
[test_ext_browserSettings_homepage.js]
skip-if = os == "android"
[test_ext_cookieBehaviors.js]
[test_ext_contextual_identities.js]
skip-if = os == "android" # Containers are not exposed to android.
[test_ext_debugging_utils.js]