зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1780747 - Register DNR schema and permissions r=rpl,geckoview-reviewers,owlish,flod
This patch adds the minimum necessary to register the declarativeNetRequest API and its permissions, behind prefs. Tests have been added/updated to verify that the permissions and API access are enforced correctly (effectiveness of preferences, API visibility, permission warnings). Before landing this, we need to register the permission warning in Android-Components too, as mentioned in the bug (i.e. bug 1671453). Differential Revision: https://phabricator.services.mozilla.com/D152503
This commit is contained in:
Родитель
4b8c434c26
Коммит
7e7534ced4
|
@ -127,6 +127,7 @@ webextPerms.description.browserSettings=Read and modify browser settings
|
|||
webextPerms.description.browsingData=Clear recent browsing history, cookies, and related data
|
||||
webextPerms.description.clipboardRead=Get data from the clipboard
|
||||
webextPerms.description.clipboardWrite=Input data to the clipboard
|
||||
webextPerms.description.declarativeNetRequest=Block content on any page
|
||||
webextPerms.description.devtools=Extend developer tools to access your data in open tabs
|
||||
webextPerms.description.downloads=Download files and read and modify the browser’s download history
|
||||
webextPerms.description.downloads.open=Open files downloaded to your computer
|
||||
|
|
|
@ -49,6 +49,7 @@ webextPerms.description.browserSettings=Read and modify browser settings
|
|||
webextPerms.description.browsingData=Clear recent browsing history, cookies, and related data
|
||||
webextPerms.description.clipboardRead=Get data from the clipboard
|
||||
webextPerms.description.clipboardWrite=Input data to the clipboard
|
||||
webextPerms.description.declarativeNetRequest=Block content on any page
|
||||
webextPerms.description.devtools=Extend developer tools to access your data in open tabs
|
||||
webextPerms.description.downloads=Download files and read and modify the browser’s download history
|
||||
webextPerms.description.downloads.open=Open files downloaded to your computer
|
||||
|
|
|
@ -210,6 +210,9 @@ if (
|
|||
}
|
||||
}
|
||||
|
||||
const PREF_DNR_ENABLED = "extensions.dnr.enabled";
|
||||
const PREF_DNR_FEEDBACK = "extensions.dnr.feedback";
|
||||
|
||||
// Message included in warnings and errors related to privileged permissions and
|
||||
// privileged manifest properties. Provides a link to the firefox-source-docs.mozilla.org
|
||||
// section related to developing and sign Privileged Add-ons.
|
||||
|
@ -263,6 +266,23 @@ function isMozillaExtension(extension) {
|
|||
return isSigned && isMozillaLineExtension;
|
||||
}
|
||||
|
||||
function isDNRPermissionAllowed(perm) {
|
||||
// DNR is under development and therefore disabled by default for now.
|
||||
if (!Services.prefs.getBoolPref(PREF_DNR_ENABLED, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// APIs tied to declarativeNetRequestFeedback are for debugging purposes and
|
||||
// are only supposed to be available when the (add-on dev) user opts in.
|
||||
if (
|
||||
perm === "declarativeNetRequestFeedback" &&
|
||||
!Services.prefs.getBoolPref(PREF_DNR_FEEDBACK, false)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Classify an individual permission from a webextension manifest
|
||||
* as a host/origin permission, an api permission, or a regular permission.
|
||||
|
@ -295,6 +315,11 @@ function classifyPermission(perm, restrictSchemes, isPrivileged) {
|
|||
return { api: match[2] };
|
||||
} else if (!isPrivileged && PRIVILEGED_PERMS.has(match[1])) {
|
||||
return { invalid: perm, privileged: true };
|
||||
} else if (
|
||||
perm.startsWith("declarativeNetRequest") &&
|
||||
!isDNRPermissionAllowed(perm)
|
||||
) {
|
||||
return { invalid: perm };
|
||||
}
|
||||
return { permission: perm };
|
||||
}
|
||||
|
|
|
@ -60,6 +60,14 @@
|
|||
["cookies"]
|
||||
]
|
||||
},
|
||||
"declarativeNetRequest": {
|
||||
"url": "chrome://extensions/content/parent/ext-declarativeNetRequest.js",
|
||||
"schema": "chrome://extensions/content/schemas/declarative_net_request.json",
|
||||
"scopes": ["addon_parent"],
|
||||
"paths": [
|
||||
["declarativeNetRequest"]
|
||||
]
|
||||
},
|
||||
"dns": {
|
||||
"url": "chrome://extensions/content/parent/ext-dns.js",
|
||||
"schema": "chrome://extensions/content/schemas/dns.json",
|
||||
|
|
|
@ -19,6 +19,7 @@ toolkit.jar:
|
|||
content/extensions/parent/ext-contextualIdentities.js (parent/ext-contextualIdentities.js)
|
||||
content/extensions/parent/ext-clipboard.js (parent/ext-clipboard.js)
|
||||
content/extensions/parent/ext-cookies.js (parent/ext-cookies.js)
|
||||
content/extensions/parent/ext-declarativeNetRequest.js (parent/ext-declarativeNetRequest.js)
|
||||
content/extensions/parent/ext-dns.js (parent/ext-dns.js)
|
||||
content/extensions/parent/ext-downloads.js (parent/ext-downloads.js)
|
||||
content/extensions/parent/ext-extension.js (parent/ext-extension.js)
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
/* 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";
|
||||
|
||||
this.declarativeNetRequest = class extends ExtensionAPI {
|
||||
getAPI(context) {
|
||||
return {
|
||||
declarativeNetRequest: {
|
||||
async testMatchOutcome(request) {
|
||||
// TODO bug 1745758: Implement rule evaluation engine.
|
||||
// Since rule registration has not been implemented yet, the result
|
||||
// is always an empty list.
|
||||
return [];
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
|
@ -0,0 +1,136 @@
|
|||
/* 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/. */
|
||||
|
||||
[
|
||||
{
|
||||
"namespace": "manifest",
|
||||
"types": [
|
||||
{
|
||||
"$extend": "Permission",
|
||||
"choices": [{
|
||||
"type": "string",
|
||||
"enum": ["declarativeNetRequest"]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"$extend": "PermissionNoPrompt",
|
||||
"choices": [{
|
||||
"type": "string",
|
||||
"enum": ["declarativeNetRequestFeedback", "declarativeNetRequestWithHostAccess"]
|
||||
}]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"namespace": "declarativeNetRequest",
|
||||
"description": "Use the declarativeNetRequest API to block or modify network requests by specifying declarative rules.",
|
||||
"permissions": ["declarativeNetRequest", "declarativeNetRequestWithHostAccess"],
|
||||
"types": [
|
||||
{
|
||||
"id": "ResourceType",
|
||||
"type": "string",
|
||||
"description": "How the requested resource will be used. Comparable to the webRequest.ResourceType type.",
|
||||
"enum": [
|
||||
"main_frame",
|
||||
"sub_frame",
|
||||
"stylesheet",
|
||||
"script",
|
||||
"image",
|
||||
"object",
|
||||
"object_subrequest",
|
||||
"xmlhttprequest",
|
||||
"xslt",
|
||||
"ping",
|
||||
"beacon",
|
||||
"xml_dtd",
|
||||
"font",
|
||||
"media",
|
||||
"websocket",
|
||||
"csp_report",
|
||||
"imageset",
|
||||
"web_manifest",
|
||||
"speculative",
|
||||
"other"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "MatchedRule",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ruleId": {
|
||||
"type": "integer",
|
||||
"description": "A matching rule's ID."
|
||||
},
|
||||
"rulesetId": {
|
||||
"type": "string",
|
||||
"description": "ID of the Ruleset this rule belongs to."
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"functions": [
|
||||
{
|
||||
"name": "testMatchOutcome",
|
||||
"type": "function",
|
||||
"description": "Checks if any of the extension's declarativeNetRequest rules would match a hypothetical request.",
|
||||
"permissions": ["declarativeNetRequestFeedback"],
|
||||
"async": "callback",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "request",
|
||||
"type": "object",
|
||||
"description": "The details of the request to test.",
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "The URL of the hypothetical request."
|
||||
},
|
||||
"initiator": {
|
||||
"type": "string",
|
||||
"description": "The initiator URL (if any) for the hypothetical request.",
|
||||
"optional": true
|
||||
},
|
||||
"method": {
|
||||
"type": "string",
|
||||
"description": "Standard HTTP method of the hypothetical request.",
|
||||
"optional": true,
|
||||
"default": "get"
|
||||
},
|
||||
"type": {
|
||||
"$ref": "ResourceType",
|
||||
"description": "The resource type of the hypothetical request."
|
||||
},
|
||||
"tabId": {
|
||||
"type": "integer",
|
||||
"description": "The ID of the tab in which the hypothetical request takes place. Does not need to correspond to a real tab ID. Default is -1, meaning that the request isn't related to a tab.",
|
||||
"optional": true,
|
||||
"default": -1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "callback",
|
||||
"type": "function",
|
||||
"description": "Called with the details of matched rules.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "result",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"matchedRules": {
|
||||
"type": "array",
|
||||
"description": "The rules (if any) that match the hypothetical request.",
|
||||
"items": {
|
||||
"$ref": "MatchedRule"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -16,6 +16,7 @@ toolkit.jar:
|
|||
content/extensions/schemas/content_scripts.json
|
||||
content/extensions/schemas/contextual_identities.json
|
||||
content/extensions/schemas/cookies.json
|
||||
content/extensions/schemas/declarative_net_request.json
|
||||
content/extensions/schemas/dns.json
|
||||
content/extensions/schemas/downloads.json
|
||||
content/extensions/schemas/events.json
|
||||
|
|
|
@ -0,0 +1,212 @@
|
|||
"use strict";
|
||||
|
||||
AddonTestUtils.init(this);
|
||||
|
||||
const PREF_DNR_FEEDBACK_DEFAULT_VALUE = Services.prefs.getBoolPref(
|
||||
"extensions.dnr.feedback",
|
||||
false
|
||||
);
|
||||
|
||||
async function testAvailability({
|
||||
allowDNRFeedback = false,
|
||||
testExpectations,
|
||||
...extensionData
|
||||
}) {
|
||||
function background(testExpectations) {
|
||||
let {
|
||||
declarativeNetRequest_available = false,
|
||||
testMatchOutcome_available = false,
|
||||
} = testExpectations;
|
||||
browser.test.assertEq(
|
||||
declarativeNetRequest_available,
|
||||
!!browser.declarativeNetRequest,
|
||||
"declarativeNetRequest API namespace availability"
|
||||
);
|
||||
browser.test.assertEq(
|
||||
testMatchOutcome_available,
|
||||
!!browser.declarativeNetRequest?.testMatchOutcome,
|
||||
"declarativeNetRequest.testMatchOutcome availability"
|
||||
);
|
||||
browser.test.sendMessage("done");
|
||||
}
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
...extensionData,
|
||||
background: `(${background})(${JSON.stringify(testExpectations)});`,
|
||||
});
|
||||
Services.prefs.setBoolPref("extensions.dnr.feedback", allowDNRFeedback);
|
||||
try {
|
||||
await extension.startup();
|
||||
await extension.awaitMessage("done");
|
||||
await extension.unload();
|
||||
} finally {
|
||||
Services.prefs.clearUserPref("extensions.dnr.feedback");
|
||||
}
|
||||
}
|
||||
|
||||
add_setup(async () => {
|
||||
// TODO bug 1782685: Remove this check.
|
||||
Assert.equal(
|
||||
Services.prefs.getBoolPref("extensions.dnr.enabled", false),
|
||||
false,
|
||||
"DNR is disabled by default"
|
||||
);
|
||||
Services.prefs.setBoolPref("extensions.dnr.enabled", true);
|
||||
});
|
||||
|
||||
// Verifies that DNR is disabled by default (until true in bug 1782685).
|
||||
add_task(
|
||||
{
|
||||
pref_set: [["extensions.dnr.enabled", false]],
|
||||
},
|
||||
async function dnr_disabled_by_default() {
|
||||
let { messages } = await promiseConsoleOutput(async () => {
|
||||
await testAvailability({
|
||||
allowDNRFeedback: PREF_DNR_FEEDBACK_DEFAULT_VALUE,
|
||||
testExpectations: {
|
||||
declarativeNetRequest_available: false,
|
||||
},
|
||||
manifest: {
|
||||
permissions: [
|
||||
"declarativeNetRequest",
|
||||
"declarativeNetRequestFeedback",
|
||||
"declarativeNetRequestWithHostAccess",
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
AddonTestUtils.checkMessages(messages, {
|
||||
expected: [
|
||||
{
|
||||
message: /Reading manifest: Invalid extension permission: declarativeNetRequest$/,
|
||||
},
|
||||
{
|
||||
message: /Reading manifest: Invalid extension permission: declarativeNetRequestFeedback/,
|
||||
},
|
||||
{
|
||||
message: /Reading manifest: Invalid extension permission: declarativeNetRequestWithHostAccess/,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
add_task(async function dnr_feedback_apis_disabled_by_default() {
|
||||
let { messages } = await promiseConsoleOutput(async () => {
|
||||
await testAvailability({
|
||||
allowDNRFeedback: PREF_DNR_FEEDBACK_DEFAULT_VALUE,
|
||||
testExpectations: {
|
||||
declarativeNetRequest_available: true,
|
||||
},
|
||||
manifest: {
|
||||
permissions: [
|
||||
"declarativeNetRequest",
|
||||
"declarativeNetRequestFeedback",
|
||||
"declarativeNetRequestWithHostAccess",
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
AddonTestUtils.checkMessages(messages, {
|
||||
expected: [
|
||||
{
|
||||
message: /Reading manifest: Invalid extension permission: declarativeNetRequestFeedback/,
|
||||
},
|
||||
],
|
||||
forbidden: [
|
||||
{
|
||||
message: /Reading manifest: Invalid extension permission: declarativeNetRequest$/,
|
||||
},
|
||||
{
|
||||
message: /Reading manifest: Invalid extension permission: declarativeNetRequestWithHostAccess/,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function with_declarativeNetRequest_permission() {
|
||||
await testAvailability({
|
||||
allowDNRFeedback: true,
|
||||
testExpectations: {
|
||||
declarativeNetRequest_available: true,
|
||||
// feature allowed, but missing declarativeNetRequestFeedback:
|
||||
testMatchOutcome_available: false,
|
||||
},
|
||||
manifest: {
|
||||
permissions: ["declarativeNetRequest"],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function with_declarativeNetRequestWithHostAccess_permission() {
|
||||
await testAvailability({
|
||||
allowDNRFeedback: true,
|
||||
testExpectations: {
|
||||
declarativeNetRequest_available: true,
|
||||
// feature allowed, but missing declarativeNetRequestFeedback:
|
||||
testMatchOutcome_available: false,
|
||||
},
|
||||
manifest: {
|
||||
permissions: ["declarativeNetRequestWithHostAccess"],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function with_all_declarativeNetRequest_permissions() {
|
||||
await testAvailability({
|
||||
allowDNRFeedback: true,
|
||||
testExpectations: {
|
||||
declarativeNetRequest_available: true,
|
||||
// feature allowed, but missing declarativeNetRequestFeedback:
|
||||
testMatchOutcome_available: false,
|
||||
},
|
||||
manifest: {
|
||||
permissions: [
|
||||
"declarativeNetRequest",
|
||||
"declarativeNetRequestWithHostAccess",
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function no_declarativeNetRequest_permission() {
|
||||
await testAvailability({
|
||||
allowDNRFeedback: true,
|
||||
testExpectations: {
|
||||
// Just declarativeNetRequestFeedback should not unlock the API.
|
||||
declarativeNetRequest_available: false,
|
||||
},
|
||||
manifest: {
|
||||
permissions: ["declarativeNetRequestFeedback"],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function with_declarativeNetRequestFeedback_permission() {
|
||||
await testAvailability({
|
||||
allowDNRFeedback: true,
|
||||
testExpectations: {
|
||||
declarativeNetRequest_available: true,
|
||||
// feature allowed, and all permissions specified:
|
||||
testMatchOutcome_available: true,
|
||||
},
|
||||
manifest: {
|
||||
permissions: ["declarativeNetRequest", "declarativeNetRequestFeedback"],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function declarativeNetRequestFeedback_without_feature() {
|
||||
await testAvailability({
|
||||
allowDNRFeedback: false,
|
||||
testExpectations: {
|
||||
declarativeNetRequest_available: true,
|
||||
// all permissions set, but DNR feedback feature not allowed.
|
||||
testMatchOutcome_available: false,
|
||||
},
|
||||
manifest: {
|
||||
permissions: ["declarativeNetRequest", "declarativeNetRequestFeedback"],
|
||||
},
|
||||
});
|
||||
});
|
|
@ -471,6 +471,65 @@ add_task(async function nativeMessaging_permission() {
|
|||
}
|
||||
});
|
||||
|
||||
add_task(async function declarativeNetRequest_unavailable_by_default() {
|
||||
let manifestPermissions = await getManifestPermissions({
|
||||
manifest: {
|
||||
permissions: ["declarativeNetRequest"],
|
||||
},
|
||||
});
|
||||
deepEqual(
|
||||
manifestPermissions,
|
||||
{ origins: [], permissions: [] },
|
||||
"Expected declarativeNetRequest permission to be ignored/stripped"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(
|
||||
{ pref_set: [["extensions.dnr.enabled", true]] },
|
||||
async function declarativeNetRequest_permission_with_warning() {
|
||||
let manifestPermissions = await getManifestPermissions({
|
||||
manifest: {
|
||||
permissions: ["declarativeNetRequest"],
|
||||
},
|
||||
});
|
||||
|
||||
deepEqual(
|
||||
manifestPermissions,
|
||||
{ origins: [], permissions: ["declarativeNetRequest"] },
|
||||
"Expected origins and permissions"
|
||||
);
|
||||
|
||||
deepEqual(
|
||||
getPermissionWarnings(manifestPermissions),
|
||||
[
|
||||
bundle.GetStringFromName(
|
||||
"webextPerms.description.declarativeNetRequest"
|
||||
),
|
||||
],
|
||||
"Expected warnings"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
add_task(
|
||||
{ pref_set: [["extensions.dnr.enabled", true]] },
|
||||
async function declarativeNetRequest_permission_without_warning() {
|
||||
let manifestPermissions = await getManifestPermissions({
|
||||
manifest: {
|
||||
permissions: ["declarativeNetRequestWithHostAccess"],
|
||||
},
|
||||
});
|
||||
|
||||
deepEqual(
|
||||
manifestPermissions,
|
||||
{ origins: [], permissions: ["declarativeNetRequestWithHostAccess"] },
|
||||
"Expected origins and permissions"
|
||||
);
|
||||
|
||||
deepEqual(getPermissionWarnings(manifestPermissions), [], "No warnings");
|
||||
}
|
||||
);
|
||||
|
||||
// Tests that the expected permission warnings are generated for a mix of host
|
||||
// permissions and API permissions, for a privileged extension that uses the
|
||||
// mozillaAddons permission.
|
||||
|
|
|
@ -626,6 +626,8 @@ const GRANTED_WITHOUT_USER_PROMPT = [
|
|||
"contextMenus",
|
||||
"contextualIdentities",
|
||||
"cookies",
|
||||
"declarativeNetRequestFeedback",
|
||||
"declarativeNetRequestWithHostAccess",
|
||||
"dns",
|
||||
"geckoProfiler",
|
||||
"identity",
|
||||
|
|
|
@ -101,6 +101,7 @@ skip-if = appname == "thunderbird" || os == "android" # Containers are not expos
|
|||
[test_ext_cors_mozextension.js]
|
||||
[test_ext_csp_frame_ancestors.js]
|
||||
[test_ext_debugging_utils.js]
|
||||
[test_ext_dnr_api.js]
|
||||
[test_ext_dns.js]
|
||||
skip-if = os == "android" # Android needs alternative for proxy.settings - bug 1723523
|
||||
[test_ext_downloads.js]
|
||||
|
|
|
@ -58,6 +58,7 @@ skip-if = toolkit == 'android' # browser_action icon testing not supported on an
|
|||
[test_ext_manifest_minimum_opera_version.js]
|
||||
[test_ext_manifest_themes.js]
|
||||
[test_ext_permission_warnings.js]
|
||||
skip-if = condprof # Bug 1783828 - condprof fails to pick up schema changes
|
||||
[test_ext_schemas.js]
|
||||
head = head.js head_schemas.js
|
||||
[test_ext_schemas_roots.js]
|
||||
|
|
Загрузка…
Ссылка в новой задаче