Bug 1262005 Rework how WebExtensions IDs are determined r?rhelmer
MozReview-Commit-ID: 37EujfhGh0U
--- a/toolkit/components/extensions/schemas/manifest.json
+++ b/toolkit/components/extensions/schemas/manifest.json
@@ -10,38 +10,32 @@
"manifest_version": {
"type": "integer",
"minimum": 2,
"maximum": 2
},
"applications": {
"type": "object",
+ "optional": true,
"properties": {
"gecko": {
- "type": "object",
- "properties": {
- "id": { "$ref": "ExtensionID" },
-
- "update_url": {
- "type": "string",
- "format": "url",
- "optional": true
- },
+ "$ref": "FirefoxSpecificProperties",
+ "optional": true
+ }
+ }
+ },
- "strict_min_version": {
- "type": "string",
- "optional": true
- },
-
- "strict_max_version": {
- "type": "string",
- "optional": true
- }
- }
+ "browser_specific_settings": {
+ "type": "object",
+ "optional": true,
+ "properties": {
+ "gecko": {
+ "$ref": "FirefoxSpecificProperties",
+ "optional": true
}
}
},
"name": {
"type": "string",
"optional": false,
"preprocess": "localize"
@@ -201,16 +195,42 @@
},
{
"type": "string",
"pattern": "(?i)^[a-z0-9-._]*@[a-z0-9-._]+$"
}
]
},
{
+ "id": "FirefoxSpecificProperties",
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "ExtensionID",
+ "optional": true
+ },
+
+ "update_url": {
+ "type": "string",
+ "format": "url",
+ "optional": true
+ },
+
+ "strict_min_version": {
+ "type": "string",
+ "optional": true
+ },
+
+ "strict_max_version": {
+ "type": "string",
+ "optional": true
+ }
+ }
+ },
+ {
"id": "MatchPattern",
"choices": [
{
"type": "string",
"enum": ["<all_urls>"]
},
{
"type": "string",
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -875,27 +875,33 @@ var loadManifestFromWebManifest = Task.a
// Read the list of available locales, and pre-load messages for
// all locales.
let locales = yield extension.initAllLocales();
// If there were any errors loading the extension, bail out now.
if (extension.errors.length)
throw new Error("Extension is invalid");
+ let bss = (manifest.browser_specific_settings && manifest.browser_specific_settings.gecko)
+ || (manifest.applications && manifest.applications.gecko) || {};
+ if (manifest.browser_specific_settings && manifest.applications) {
+ logger.warn("Ignoring applications property in manifest");
+ }
+
let addon = new AddonInternal();
- addon.id = manifest.applications.gecko.id;
+ addon.id = bss.id;
addon.version = manifest.version;
addon.type = "webextension";
addon.unpack = false;
addon.strictCompatibility = true;
addon.bootstrap = true;
addon.hasBinaryComponents = false;
addon.multiprocessCompatible = true;
addon.internalName = null;
- addon.updateURL = manifest.applications.gecko.update_url;
+ addon.updateURL = bss.update_url;
addon.updateKey = null;
addon.optionsURL = null;
addon.optionsType = null;
addon.aboutURL = null;
if (manifest.options_ui) {
addon.optionsURL = extension.getURL(manifest.options_ui.page);
if (manifest.options_ui.open_in_tab)
@@ -932,19 +938,19 @@ var loadManifestFromWebManifest = Task.a
addon.defaultLocale = getLocale(extension.defaultLocale);
addon.locales = Array.from(locales.keys(), getLocale);
delete addon.defaultLocale.locales;
addon.targetApplications = [{
id: TOOLKIT_ID,
- minVersion: (manifest.applications.gecko.strict_min_version ||
+ minVersion: (bss.strict_min_version ||
AddonManagerPrivate.webExtensionsMinPlatformVersion),
- maxVersion: manifest.applications.gecko.strict_max_version || "*",
+ maxVersion: bss.strict_max_version || "*",
}];
addon.targetPlatforms = [];
addon.userDisabled = false;
addon.softDisabled = addon.blocklistState == Blocklist.STATE_SOFTBLOCKED;
return addon;
});
@@ -1309,24 +1315,39 @@ var loadManifestFromDir = Task.async(fun
let file = getManifestFileForDir(aDir);
if (!file) {
throw new Error("Directory " + aDir.path + " does not contain a valid " +
"install manifest");
}
let uri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
- let addon = file.leafName == FILE_WEB_MANIFEST ?
- yield loadManifestFromWebManifest(uri) :
- loadFromRDF(uri);
+ let addon;
+ if (file.leafName == FILE_WEB_MANIFEST) {
+ addon = yield loadManifestFromWebManifest(uri);
+ if (!addon.id) {
+ if (aInstallLocation == TemporaryInstallLocation) {
+ let id = Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator)
+ .generateUUID().toString();
+ logger.info(`Generated temporary id ${id} for ${aDir.path}`);
+ addon.id = id;
+ } else {
+ addon.id = aDir.leafName;
+ }
+ }
+ } else {
+ addon = loadFromRDF(uri);
+ }
addon._sourceBundle = aDir.clone();
addon._installLocation = aInstallLocation;
addon.size = getFileSize(aDir);
- addon.signedState = yield verifyDirSignedState(aDir, addon);
+ addon.signedState = yield verifyDirSignedState(aDir, addon)
+ .then(({signedState}) => signedState);
addon.appDisabled = !isUsableAddon(addon);
defineSyncGUID(addon);
return addon;
});
/**
@@ -1375,29 +1396,35 @@ var loadManifestFromZipReader = Task.asy
let entry = getManifestEntryForZipReader(aZipReader);
if (!entry) {
throw new Error("File " + aZipReader.file.path + " does not contain a valid " +
"install manifest");
}
let uri = buildJarURI(aZipReader.file, entry);
- let addon = entry == FILE_WEB_MANIFEST ?
+ let isWebExtension = (entry == FILE_WEB_MANIFEST);
+
+ let addon = isWebExtension ?
yield loadManifestFromWebManifest(uri) :
loadFromRDF(uri);
addon._sourceBundle = aZipReader.file;
addon._installLocation = aInstallLocation;
addon.size = 0;
let entries = aZipReader.findEntries(null);
while (entries.hasMore())
addon.size += aZipReader.getEntry(entries.getNext()).realSize;
- addon.signedState = yield verifyZipSignedState(aZipReader.file, addon);
+ let {signedState, cert} = yield verifyZipSignedState(aZipReader.file, addon);
+ addon.signedState = signedState;
+ if (isWebExtension && !addon.id && cert) {
+ addon.id = cert.commonName;
+ }
addon.appDisabled = !isUsableAddon(addon);
defineSyncGUID(addon);
return addon;
});
/**
@@ -1566,32 +1593,32 @@ function verifyZipSigning(aZip, aCertifi
}
/**
* Returns the signedState for a given return code and certificate by verifying
* it against the expected ID.
*/
function getSignedStatus(aRv, aCert, aAddonID) {
let expectedCommonName = aAddonID;
- if (aAddonID.length > 64) {
+ if (aAddonID && aAddonID.length > 64) {
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
let data = converter.convertToByteArray(aAddonID, {});
let crypto = Cc["@mozilla.org/security/hash;1"].
createInstance(Ci.nsICryptoHash);
crypto.init(Ci.nsICryptoHash.SHA256);
crypto.update(data, data.length);
expectedCommonName = getHashStringForCrypto(crypto);
}
switch (aRv) {
case Cr.NS_OK:
- if (expectedCommonName != aCert.commonName)
+ if (expectedCommonName && expectedCommonName != aCert.commonName)
return AddonManager.SIGNEDSTATE_BROKEN;
let hotfixID = Preferences.get(PREF_EM_HOTFIX_ID, undefined);
if (hotfixID && hotfixID == aAddonID && Preferences.get(PREF_EM_CERT_CHECKATTRIBUTES, false)) {
// The hotfix add-on has some more rigorous certificate checks
try {
CertUtils.validateCert(aCert,
CertUtils.readCertPrefs(PREF_EM_HOTFIX_CERTS));
@@ -1654,32 +1681,40 @@ let gCertDB = Cc["@mozilla.org/security/
/**
* Verifies that a zip file's contents are all correctly signed by an
* AMO-issued certificate
*
* @param aFile
* the xpi file to check
* @param aAddon
* the add-on object to verify
- * @return a Promise that resolves to an AddonManager.SIGNEDSTATE_* constant.
+ * @return a Promise that resolves to an object with properties:
+ * signedState: an AddonManager.SIGNEDSTATE_* constant
+ * cert: an nsIX509Cert
*/
function verifyZipSignedState(aFile, aAddon) {
if (!shouldVerifySignedState(aAddon))
- return Promise.resolve(AddonManager.SIGNEDSTATE_NOT_REQUIRED);
+ return Promise.resolve({
+ signedState: AddonManager.SIGNEDSTATE_NOT_REQUIRED,
+ cert: null
+ });
let root = Ci.nsIX509CertDB.AddonsPublicRoot;
if (!REQUIRE_SIGNING && Preferences.get(PREF_XPI_SIGNATURES_DEV_ROOT, false))
root = Ci.nsIX509CertDB.AddonsStageRoot;
return new Promise(resolve => {
let callback = {
openSignedAppFileFinished: function(aRv, aZipReader, aCert) {
if (aZipReader)
aZipReader.close();
- resolve(getSignedStatus(aRv, aCert, aAddon.id));
+ resolve({
+ signedState: getSignedStatus(aRv, aCert, aAddon.id),
+ cert: aCert
+ });
}
};
// This allows the certificate DB to get the raw JS callback object so the
// test code can pass through objects that XPConnect would reject.
callback.wrappedJSObject = callback;
gCertDB.openSignedAppFileAsync(root, aFile, callback);
});
@@ -1688,30 +1723,38 @@ function verifyZipSignedState(aFile, aAd
/**
* Verifies that a directory's contents are all correctly signed by an
* AMO-issued certificate
*
* @param aDir
* the directory to check
* @param aAddon
* the add-on object to verify
- * @return a Promise that resolves to an AddonManager.SIGNEDSTATE_* constant.
+ * @return a Promise that resolves to an object with properties:
+ * signedState: an AddonManager.SIGNEDSTATE_* constant
+ * cert: an nsIX509Cert
*/
function verifyDirSignedState(aDir, aAddon) {
if (!shouldVerifySignedState(aAddon))
- return Promise.resolve(AddonManager.SIGNEDSTATE_NOT_REQUIRED);
+ return Promise.resolve({
+ signedState: AddonManager.SIGNEDSTATE_NOT_REQUIRED,
+ cert: null,
+ });
let root = Ci.nsIX509CertDB.AddonsPublicRoot;
if (!REQUIRE_SIGNING && Preferences.get(PREF_XPI_SIGNATURES_DEV_ROOT, false))
root = Ci.nsIX509CertDB.AddonsStageRoot;
return new Promise(resolve => {
let callback = {
verifySignedDirectoryFinished: function(aRv, aCert) {
- resolve(getSignedStatus(aRv, aCert, aAddon.id));
+ resolve({
+ signedState: getSignedStatus(aRv, aCert, aAddon.id),
+ cert: null,
+ });
}
};
// This allows the certificate DB to get the raw JS callback object so the
// test code can pass through objects that XPConnect would reject.
callback.wrappedJSObject = callback;
gCertDB.verifySignedDirectoryAsync(root, aDir, callback);
});
@@ -1723,19 +1766,19 @@ function verifyDirSignedState(aDir, aAdd
*
* @param aBundle
* the nsIFile for the bundle to check, either a directory or zip file
* @param aAddon
* the add-on object to verify
* @return a Promise that resolves to an AddonManager.SIGNEDSTATE_* constant.
*/
function verifyBundleSignedState(aBundle, aAddon) {
- if (aBundle.isFile())
- return verifyZipSignedState(aBundle, aAddon);
- return verifyDirSignedState(aBundle, aAddon);
+ let promise = aBundle.isFile() ? verifyZipSignedState(aBundle, aAddon)
+ : verifyDirSignedState(aBundle, aAddon);
+ return promise.then(({signedState}) => signedState);
}
/**
* Replaces %...% strings in an addon url (update and updateInfo) with
* appropriate values.
*
* @param aAddon
* The AddonInternal representing the add-on
new file mode 100644
index 0000000000000000000000000000000000000000..6b4abaa6919af1df56045f2fe71fdb37218f1c0b
GIT binary patch
literal 4182
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
--- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension.js
@@ -185,35 +185,16 @@ add_task(function* test_manifest_localiz
addon = yield promiseAddonByID(ID);
equal(addon.name, "Web Extensiøn foo ☹");
equal(addon.description, "Descriptïon bar ☹ of add-on");
addon.uninstall();
});
-// Missing ID should cause a failure
-add_task(function*() {
- writeWebManifestForExtension({
- name: "Web Extension Name",
- version: "1.0",
- manifest_version: 2,
- }, profileDir, ID);
-
- yield promiseRestartManager();
-
- let addon = yield promiseAddonByID(ID);
- do_check_eq(addon, null);
-
- let file = getFileForAddon(profileDir, ID);
- do_check_false(file.exists());
-
- yield promiseRestartManager();
-});
-
// Missing version should cause a failure
add_task(function*() {
writeWebManifestForExtension({
name: "Web Extension Name",
manifest_version: 2,
applications: {
gecko: {
id: ID
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js
@@ -0,0 +1,112 @@
+function run_test() {
+ run_next_test();
+}
+
+let profileDir;
+add_task(function* setup() {
+ profileDir = gProfD.clone();
+ profileDir.append("extensions");
+
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
+ startupManager();
+});
+
+const IMPLICIT_ID_XPI = "data/webext-implicit-id.xpi";
+const IMPLICIT_ID_ID = "webext_implicit_id@tests.mozilla.org";
+
+// webext-implicit-id.xpi has a minimal manifest with no
+// applications or browser_specific_settings, so its id comes
+// from its signature, which should be the ID constant defined below.
+add_task(function* test_implicit_id() {
+ let addon = yield promiseAddonByID(IMPLICIT_ID_ID);
+ do_check_eq(addon, null);
+
+ let xpifile = do_get_file(IMPLICIT_ID_XPI);
+ yield promiseInstallAllFiles([xpifile]);
+
+ addon = yield promiseAddonByID(IMPLICIT_ID_ID);
+ do_check_neq(addon, null);
+
+ addon.uninstall();
+});
+
+// We should also be able to install webext-implicit-id.xpi temporarily
+// and it should look just like the regular install (ie, the ID should
+// come from the signature)
+add_task(function* test_implicit_id_temp() {
+ let addon = yield promiseAddonByID(IMPLICIT_ID_ID);
+ do_check_eq(addon, null);
+
+ let xpifile = do_get_file(IMPLICIT_ID_XPI);
+ yield AddonManager.installTemporaryAddon(xpifile);
+
+ addon = yield promiseAddonByID(IMPLICIT_ID_ID);
+ do_check_neq(addon, null);
+
+ addon.uninstall();
+});
+
+// Test that we can get the ID from browser_specific_settings
+add_task(function* test_bss_id() {
+ const ID = "webext_bss_id@tests.mozilla.org";
+
+ let manifest = {
+ name: "bss test",
+ description: "test that ID may be in browser_specific_settings",
+ manifest_version: 2,
+ version: "1.0",
+
+ browser_specific_settings: {
+ gecko: {
+ id: ID
+ }
+ }
+ };
+
+ let addon = yield promiseAddonByID(ID);
+ do_check_eq(addon, null);
+
+ writeWebManifestForExtension(manifest, profileDir, ID);
+ yield promiseRestartManager();
+
+ addon = yield promiseAddonByID(ID);
+ do_check_neq(addon, null);
+
+ addon.uninstall();
+});
+
+// Test that if we have IDs in both browser_specific_settings and applications,
+// that we prefer the ID in browser_specific_settings.
+add_task(function* test_two_ids() {
+ const GOOD_ID = "two_ids@tests.mozilla.org";
+ const BAD_ID = "i_am_obsolete@tests.mozilla.org";
+
+ let manifest = {
+ name: "two id test",
+ description: "test a web extension with ids in both applications and browser_specific_settings",
+ manifest_version: 2,
+ version: "1.0",
+
+ applications: {
+ gecko: {
+ id: BAD_ID
+ }
+ },
+
+ browser_specific_settings: {
+ gecko: {
+ id: GOOD_ID
+ }
+ }
+ }
+
+ writeWebManifestForExtension(manifest, profileDir, GOOD_ID);
+ yield promiseRestartManager();
+
+ let addon = yield promiseAddonByID(BAD_ID);
+ do_check_eq(addon, null);
+ addon = yield promiseAddonByID(GOOD_ID);
+ do_check_neq(addon, null);
+
+ addon.uninstall();
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_paths.js
@@ -0,0 +1,55 @@
+function run_test() {
+ run_next_test();
+}
+
+let profileDir;
+add_task(function* setup() {
+ profileDir = gProfD.clone();
+ profileDir.append("extensions");
+
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
+ startupManager();
+});
+
+// When installing an unpacked addon we derive the ID from the
+// directory name. Make sure that if the directoy name is not a valid
+// addon ID that we reject it.
+add_task(function* test_bad_unpacked_path() {
+ let MANIFEST_ID = "webext_bad_path@tests.mozilla.org";
+
+ let manifest = {
+ name: "path test",
+ description: "test of a bad directory name",
+ manifest_version: 2,
+ version: "1.0",
+
+ browser_specific_settings: {
+ gecko: {
+ id: MANIFEST_ID
+ }
+ }
+ };
+
+ const directories = [
+ "not a valid ID",
+ '"quotes"@tests.mozilla.org',
+ ];
+
+ for (let dir of directories) {
+ try {
+ writeWebManifestForExtension(manifest, profileDir, dir);
+ } catch (ex) {
+ // This can fail if the underlying filesystem (looking at you windows)
+ // doesn't handle some of the characters in the ID. In that case,
+ // just ignore this test on this platform.
+ continue;
+ }
+ yield promiseRestartManager();
+
+ let addon = yield promiseAddonByID(dir);
+ do_check_eq(addon, null);
+ addon = yield promiseAddonByID(MANIFEST_ID);
+ do_check_eq(addon, null);
+ }
+});
+
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
@@ -300,16 +300,17 @@ run-sequentially = Uses global XCurProcD
# Bug 676992: test consistently hangs on Android
skip-if = os == "android"
run-sequentially = Uses global XCurProcD dir.
[test_overrideblocklist.js]
run-sequentially = Uses global XCurProcD dir.
[test_sourceURI.js]
[test_webextension_icons.js]
[test_webextension.js]
+[test_webextension_install.js]
[test_bootstrap_globals.js]
[test_bug1180901_2.js]
skip-if = os != "win"
[test_bug1180901.js]
skip-if = os != "win"
[test_e10s_restartless.js]
[test_switch_os.js]
# Bug 1246231
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-unpack.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-unpack.ini
@@ -1,9 +1,11 @@
[DEFAULT]
head = head_addons.js head_unpack.js
tail =
firefox-appdir = browser
skip-if = toolkit == 'android' || toolkit == 'gonk'
dupe-manifest =
tags = addons
+[test_webextension_paths.js]
+
[include:xpcshell-shared.ini]