Bug 1273229 - auto-generate IDs for temp installs from dir path. r=kmag
MozReview-Commit-ID: JFuEVUZs2Ci
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -239,16 +239,23 @@ const RESTARTLESS_TYPES = new Set([
]);
const SIGNED_TYPES = new Set([
"webextension",
"extension",
"experiment",
]);
+// This is a random number array that can be used as "salt" when generating
+// an automatic ID based on the directory path of an add-on. It will prevent
+// someone from creating an ID for a permanent add-on that could be replaced
+// by a temporary add-on (because that would be confusing, I guess).
+const TEMP_INSTALL_ID_GEN_SESSION =
+ new Uint8Array(Float64Array.of(Math.random()).buffer);
+
// Whether add-on signing is required.
function mustSign(aType) {
if (!SIGNED_TYPES.has(aType))
return false;
return REQUIRE_SIGNING || Preferences.get(PREF_XPI_SIGNATURES_REQUIRED, false);
}
// Keep track of where we are in startup for telemetry
@@ -1324,21 +1331,29 @@ var loadManifestFromDir = Task.async(fun
let uri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
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;
+ // Generate a unique ID based on the directory path of
+ // this temporary add-on location.
+ const hasher = Cc["@mozilla.org/security/hash;1"]
+ .createInstance(Ci.nsICryptoHash);
+ hasher.init(hasher.SHA1);
+ const data = new TextEncoder().encode(aDir.path);
+ // Make it so this ID cannot be guessed.
+ const sess = TEMP_INSTALL_ID_GEN_SESSION;
+ hasher.update(sess, sess.length);
+ hasher.update(data, data.length);
+ addon.id = `${getHashStringForCrypto(hasher)}@temporary-addon`;
+ logger.info(
+ `Generated temp id ${addon.id} (${sess.join("")}) for ${aDir.path}`);
} else {
addon.id = aDir.leafName;
}
}
} else {
addon = loadFromRDF(uri);
}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js
@@ -41,16 +41,81 @@ add_task(function* test_implicit_id_temp
yield AddonManager.installTemporaryAddon(xpifile);
addon = yield promiseAddonByID(IMPLICIT_ID_ID);
do_check_neq(addon, null);
addon.uninstall();
});
+// We should be able to temporarily install an unsigned web extension
+// that does not have an ID in its manifest.
+add_task(function* test_unsigned_no_id_temp_install() {
+ if (!TEST_UNPACKED) {
+ do_print("This test does not apply when using packed extensions");
+ return;
+ }
+ const manifest = {
+ name: "no ID",
+ description: "extension without an ID",
+ manifest_version: 2,
+ version: "1.0"
+ };
+
+ const addonDir = writeWebManifestForExtension(manifest, gTmpD,
+ "the-addon-sub-dir");
+ const addon = yield AddonManager.installTemporaryAddon(addonDir);
+ ok(addon.id, "ID should have been auto-generated");
+
+ // Install the same directory again, as if re-installing or reloading.
+ const secondAddon = yield AddonManager.installTemporaryAddon(addonDir);
+ // The IDs should be the same.
+ equal(secondAddon.id, addon.id);
+
+ secondAddon.uninstall();
+ addonDir.remove(true);
+});
+
+// We should be able to install two extensions from manifests without IDs
+// at different locations and get two unique extensions.
+add_task(function* test_multiple_no_id_extensions() {
+ if (!TEST_UNPACKED) {
+ do_print("This test does not apply when using packed extensions");
+ return;
+ }
+ const manifest = {
+ name: "no ID",
+ description: "extension without an ID",
+ manifest_version: 2,
+ version: "1.0"
+ };
+
+ const firstAddonDir = writeWebManifestForExtension(manifest, gTmpD,
+ "addon-sub-dir-one");
+ const secondAddonDir = writeWebManifestForExtension(manifest, gTmpD,
+ "addon-sub-dir-two");
+ const [firstAddon, secondAddon] = yield Promise.all([
+ AddonManager.installTemporaryAddon(firstAddonDir),
+ AddonManager.installTemporaryAddon(secondAddonDir)
+ ]);
+
+ const allAddons = yield new Promise(resolve => {
+ AddonManager.getAllAddons(addons => resolve(addons));
+ });
+ do_print(`Found these add-ons: ${allAddons.map(a => a.name).join(", ")}`);
+ const filtered = allAddons.filter(addon => addon.name === manifest.name);
+ // Make sure we have two add-ons by the same name.
+ equal(filtered.length, 2);
+
+ firstAddon.uninstall();
+ firstAddonDir.remove(true);
+ secondAddon.uninstall();
+ secondAddonDir.remove(true);
+});
+
// 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,