Bug 1314361: Part 2 - Stop using addonId origin attribute for permission checks. r=billm draft
authorKris Maglione <maglione.k@gmail.com>
Fri, 04 Nov 2016 15:16:50 -0700
changeset 489859 abaffcc44d5653e6e479940302d0aae71b0bb880
parent 489858 ead57be1ab6a6fb5a7627dea16d6fec2261c950d
child 489860 f66ee3c535ed051b89c110ce09dadca22a48996d
push id46924
push usermaglione.k@gmail.com
push dateSun, 26 Feb 2017 21:23:01 +0000
reviewersbillm
bugs1314361
milestone54.0a1
Bug 1314361: Part 2 - Stop using addonId origin attribute for permission checks. r=billm MozReview-Commit-ID: 2SUmrkFYxYw
caps/BasePrincipal.cpp
caps/tests/mochitest/test_addonMayLoad.html
caps/tests/mochitest/test_extensionURL.html
devtools/server/actors/tab.js
devtools/server/actors/webextension.js
toolkit/components/extensions/ExtensionContent.jsm
toolkit/components/extensions/ExtensionManagement.jsm
toolkit/components/extensions/test/xpcshell/test_getAPILevelForWindow.js
--- a/caps/BasePrincipal.cpp
+++ b/caps/BasePrincipal.cpp
@@ -610,25 +610,29 @@ BasePrincipal::GetUnknownAppId(bool* aUn
 {
   *aUnknownAppId = AppId() == nsIScriptSecurityManager::UNKNOWN_APP_ID;
   return NS_OK;
 }
 
 bool
 BasePrincipal::AddonHasPermission(const nsAString& aPerm)
 {
-  if (mOriginAttributes.mAddonId.IsEmpty()) {
+  nsAutoString addonId;
+  NS_ENSURE_SUCCESS(GetAddonId(addonId), false);
+
+  if (addonId.IsEmpty()) {
     return false;
   }
+
   nsCOMPtr<nsIAddonPolicyService> aps =
     do_GetService("@mozilla.org/addons/policy-service;1");
   NS_ENSURE_TRUE(aps, false);
 
   bool retval = false;
-  nsresult rv = aps->AddonHasPermission(mOriginAttributes.mAddonId, aPerm, &retval);
+  nsresult rv = aps->AddonHasPermission(addonId, aPerm, &retval);
   NS_ENSURE_SUCCESS(rv, false);
   return retval;
 }
 
 already_AddRefed<BasePrincipal>
 BasePrincipal::CreateCodebasePrincipal(nsIURI* aURI, const OriginAttributes& aAttrs)
 {
   // If the URI is supposed to inherit the security context of whoever loads it,
@@ -697,21 +701,24 @@ BasePrincipal::CloneStrippingUserContext
   NS_ENSURE_SUCCESS(rv, nullptr);
 
   return BasePrincipal::CreateCodebasePrincipal(uri, attrs);
 }
 
 bool
 BasePrincipal::AddonAllowsLoad(nsIURI* aURI, bool aExplicit /* = false */)
 {
-  if (mOriginAttributes.mAddonId.IsEmpty()) {
+  nsAutoString addonId;
+  NS_ENSURE_SUCCESS(GetAddonId(addonId), false);
+
+  if (addonId.IsEmpty()) {
     return false;
   }
 
   nsCOMPtr<nsIAddonPolicyService> aps = do_GetService("@mozilla.org/addons/policy-service;1");
   NS_ENSURE_TRUE(aps, false);
 
   bool allowed = false;
-  nsresult rv = aps->AddonMayLoadURI(mOriginAttributes.mAddonId, aURI, aExplicit, &allowed);
+  nsresult rv = aps->AddonMayLoadURI(addonId, aURI, aExplicit, &allowed);
   return NS_SUCCEEDED(rv) && allowed;
 }
 
 } // namespace mozilla
--- a/caps/tests/mochitest/test_addonMayLoad.html
+++ b/caps/tests/mochitest/test_addonMayLoad.html
@@ -15,71 +15,59 @@ https://bugzilla.mozilla.org/show_bug.cg
   const Cc = Components.classes;
   const Ci = Components.interfaces;
   const Cu = Components.utils;
   Cu.import("resource://gre/modules/Services.jsm");
   let ssm = Services.scriptSecurityManager;
   let aps = Cc["@mozilla.org/addons/policy-service;1"].getService(Ci.nsIAddonPolicyService).wrappedJSObject;
 
   SimpleTest.waitForExplicitFinish();
+  let oldAddonIdCallback = aps.setExtensionURIToAddonIdCallback(uri => uri.host);
   SimpleTest.registerCleanupFunction(function() {
-    aps.setAddonLoadURICallback('addonA', null);
-    aps.setAddonLoadURICallback('addonB', null);
+    aps.setAddonLoadURICallback('addona', null);
+    aps.setAddonLoadURICallback('addonb', null);
+    aps.setExtensionURIToAddonIdCallback(oldAddonIdCallback);
   });
 
   function tryLoad(sb, uri) {
     let p = new Promise(function(resolve, reject) {
       Cu.exportFunction(resolve, sb, { defineAs: "finish" });
       Cu.exportFunction(reject, sb, { defineAs: "error" });
       sb.eval("try { (function () { " +
               "  var xhr = new XMLHttpRequest();" +
               "  xhr.onreadystatechange = function() { if (xhr.readyState == XMLHttpRequest.DONE) { finish(xhr.status == 200); } };" +
               "  xhr.open('GET', '" + uri + "', true);" +
               "  xhr.send();" +
               "})() } catch (e) { error(e); }");
     });
     return p;
   }
 
-  let exampleCom_addonA = new Cu.Sandbox(ssm.createCodebasePrincipal(Services.io.newURI('http://example.com'), {addonId: 'addonA'}),
-                                         {wantGlobalProperties: ['XMLHttpRequest']});
-  let nullPrin_addonA = new Cu.Sandbox(ssm.createNullPrincipal({addonId: 'addonA'}),
-                                       {wantGlobalProperties: ['XMLHttpRequest']});
-  let exampleCom_addonB = new Cu.Sandbox(ssm.createCodebasePrincipal(Services.io.newURI('http://example.com'), {addonId: 'addonB'}),
-                                         {wantGlobalProperties: ['XMLHttpRequest']});
+  let addonA = new Cu.Sandbox(ssm.createCodebasePrincipal(Services.io.newURI('moz-extension://addonA/'), {}),
+                              {wantGlobalProperties: ['XMLHttpRequest']});
+  let addonB = new Cu.Sandbox(ssm.createCodebasePrincipal(Services.io.newURI('moz-extension://addonB/'), {}),
+                              {wantGlobalProperties: ['XMLHttpRequest']});
 
   function uriForDomain(d) { return d + '/tests/caps/tests/mochitest/file_data.txt' }
 
-  tryLoad(exampleCom_addonA, uriForDomain('http://example.com'))
+  tryLoad(addonA, uriForDomain('http://test1.example.org'))
   .then(function(success) {
-    ok(success, "same-origin load should succeed for addon A");
-    return tryLoad(nullPrin_addonA, uriForDomain('http://example.com'));
-  }).then(function(success) {
-    ok(!success, "null-principal load should fail for addon A");
-    return tryLoad(exampleCom_addonB, uriForDomain('http://example.com'));
-  }).then(function(success) {
-    ok(success, "same-origin load should succeed for addon B");
-    return tryLoad(exampleCom_addonA, uriForDomain('http://test1.example.org'));
-  }).then(function(success) {
     ok(!success, "cross-origin load should fail for addon A");
-    aps.setAddonLoadURICallback('addonA', function(uri) { return /test1/.test(uri.host); });
-    aps.setAddonLoadURICallback('addonB', function(uri) { return /test2/.test(uri.host); });
-    return tryLoad(exampleCom_addonA, uriForDomain('http://test1.example.org'));
+    aps.setAddonLoadURICallback('addona', function(uri) { return /test1/.test(uri.host); });
+    aps.setAddonLoadURICallback('addonb', function(uri) { return /test2/.test(uri.host); });
+    return tryLoad(addonA, uriForDomain('http://test1.example.org'));
   }).then(function(success) {
     ok(success, "whitelisted cross-origin load of test1 should succeed for addon A");
-    return tryLoad(nullPrin_addonA, uriForDomain('http://test1.example.org'));
-  }).then(function(success) {
-    ok(!success, "whitelisted null principal load of test1 should still fail for addon A");
-    return tryLoad(exampleCom_addonB, uriForDomain('http://test1.example.org'));
+    return tryLoad(addonB, uriForDomain('http://test1.example.org'));
   }).then(function(success) {
     ok(!success, "non-whitelisted cross-origin load of test1 should fail for addon B");
-    return tryLoad(exampleCom_addonB, uriForDomain('http://test2.example.org'));
+    return tryLoad(addonB, uriForDomain('http://test2.example.org'));
   }).then(function(success) {
     ok(success, "whitelisted cross-origin load of test2 should succeed for addon B");
-    return tryLoad(exampleCom_addonA, uriForDomain('http://test2.example.org'));
+    return tryLoad(addonA, uriForDomain('http://test2.example.org'));
   }).then(function(success) {
     ok(!success, "non-whitelisted cross-origin load of test2 should fail for addon A");
     SimpleTest.finish();
   }, function(e) {
     ok(false, "Rejected promise chain: " + e);
     SimpleTest.finish();
   });
 
--- a/caps/tests/mochitest/test_extensionURL.html
+++ b/caps/tests/mochitest/test_extensionURL.html
@@ -88,17 +88,17 @@ https://bugzilla.mozilla.org/show_bug.cg
       var p = new Promise(function(resolve, reject) {
         ifr.onload = function() {
           ok(true, 'Loaded ' + url);
           var prin = SpecialPowers.wrap(ifr.contentWindow).document.nodePrincipal;
           function stripTrailingSlash(s) { return s.replace(/\/$/, ''); };
           is(stripTrailingSlash(prin.URI.spec), url, 'Principal uri is correct: ' + url);
           function stripPath(s) { return s.replace(/(.*\/\/.+)\/.*/, '$1'); };
           is(prin.originNoSuffix, stripPath(url), 'Principal origin is correct: ' + prin.originNoSuffix);
-          is(prin.originAttributes.addonId, 'imaginaryaddon-' + url[url.indexOf('/') + 2], 'addonId is correct');
+          is(prin.addonId, 'imaginaryaddon-' + url[url.indexOf('/') + 2], 'addonId is correct');
           if (/_blank/.test(url)) {
             is(SpecialPowers.wrap(ifr.contentWindow).document.documentElement.innerHTML,
                '<head></head><body></body>', 'blank document looks right');
           } else {
             is(SpecialPowers.wrap(ifr.contentWindow).document.title, 'resource test file',
                'document looks right');
           }
           ifr.remove();
--- a/devtools/server/actors/tab.js
+++ b/devtools/server/actors/tab.js
@@ -728,17 +728,17 @@ TabActor.prototype = {
       if (window.parent && window != this._originalWindow) {
         parentID = window.parent
                          .QueryInterface(Ci.nsIInterfaceRequestor)
                          .getInterface(Ci.nsIDOMWindowUtils)
                          .outerWindowID;
       }
 
       // Collect the addonID from the document origin attributes.
-      let addonID = window.document.nodePrincipal.originAttributes.addonId;
+      let addonID = window.document.nodePrincipal.addonId;
 
       return {
         id,
         parentID,
         addonID,
         url: window.location.href,
         title: window.document.title,
       };
--- a/devtools/server/actors/webextension.js
+++ b/devtools/server/actors/webextension.js
@@ -304,17 +304,17 @@ WebExtensionActor.prototype._allowSource
 /**
  * Return true if the given global is associated with this addon and should be
  * added as a debuggee, false otherwise.
  */
 WebExtensionActor.prototype._shouldAddNewGlobalAsDebuggee = function (newGlobal) {
   const global = unwrapDebuggerObjectGlobal(newGlobal);
 
   if (global instanceof Ci.nsIDOMWindow) {
-    return global.document.nodePrincipal.originAttributes.addonId == this.id;
+    return global.document.nodePrincipal.addonId == this.id;
   }
 
   try {
     // This will fail for non-Sandbox objects, hence the try-catch block.
     let metadata = Cu.getSandboxMetadata(global);
     if (metadata) {
       return metadata.addonID === this.id;
     }
--- a/toolkit/components/extensions/ExtensionContent.jsm
+++ b/toolkit/components/extensions/ExtensionContent.jsm
@@ -361,17 +361,17 @@ class ContentScriptContextChild extends 
         isWebExtensionContentScript: true,
       });
     } else {
       // This metadata is required by the Developer Tools, in order for
       // the content script to be associated with both the extension and
       // the tab holding the content page.
       let metadata = {
         "inner-window-id": this.innerWindowID,
-        addonId: attrs.addonId,
+        addonId: extensionPrincipal.addonId,
       };
 
       this.sandbox = Cu.Sandbox(principal, {
         metadata,
         sandboxPrototype: contentWindow,
         sameZoneAs: contentWindow,
         wantXrays: true,
         isWebExtensionContentScript: true,
--- a/toolkit/components/extensions/ExtensionManagement.jsm
+++ b/toolkit/components/extensions/ExtensionManagement.jsm
@@ -225,33 +225,33 @@ var Service = {
       html += `<script src="${script}"></script>\n`;
     }
     html += "</body>\n</html>\n";
 
     return "data:text/html;charset=utf-8," + encodeURIComponent(html);
   },
 
   // Finds the add-on ID associated with a given moz-extension:// URI.
-  // This is used to set the addonId on the originAttributes for the
-  // nsIPrincipal attached to the URI.
+  // This is used to set the addonId on the for the nsIPrincipal
+  // attached to the URI.
   extensionURIToAddonID(uri) {
     let uuid = uri.host;
     let extension = this.uuidMap.get(uuid);
     return extension ? extension.id : undefined;
   },
 };
 
 // API Levels Helpers
 
 // Find the add-on associated with this document via the
-// principal's originAttributes. This value is computed by
+// principal's addonId attribute. This value is computed by
 // extensionURIToAddonID, which ensures that we don't inject our
 // API into webAccessibleResources or remote web pages.
 function getAddonIdForWindow(window) {
-  return Cu.getObjectPrincipal(window).originAttributes.addonId;
+  return Cu.getObjectPrincipal(window).addonId;
 }
 
 const API_LEVELS = Object.freeze({
   NO_PRIVILEGES: 0,
   CONTENTSCRIPT_PRIVILEGES: 1,
   FULL_PRIVILEGES: 2,
 });
 
--- a/toolkit/components/extensions/test/xpcshell/test_getAPILevelForWindow.js
+++ b/toolkit/components/extensions/test/xpcshell/test_getAPILevelForWindow.js
@@ -1,27 +1,42 @@
 "use strict";
 
-Cu.import("resource://gre/modules/ExtensionManagement.jsm");
+const {Service} = Cu.import("resource://gre/modules/ExtensionManagement.jsm", {});
 Cu.import("resource://gre/modules/Services.jsm");
 
+XPCOMUtils.defineLazyServiceGetter(this, "uuidGen",
+                                   "@mozilla.org/uuid-generator;1",
+                                   "nsIUUIDGenerator");
+
 function createWindowWithAddonId(addonId) {
-  let baseURI = Services.io.newURI("about:blank");
-  let originAttributes = {addonId};
+  const uuid = uuidGen.generateUUID().number.slice(1, -1);
+
+  const url = `moz-extension://${uuid}/`;
+
+  // Set the add-on ID for this moz-extensions: URL.
+  Service.uuidMap.set(uuid, {id: addonId});
+  do_register_cleanup(() => {
+    Service.uuidMap.delete(uuid);
+  });
+
+  let baseURI = Services.io.newURI(url);
   let principal = Services.scriptSecurityManager
-                          .createCodebasePrincipal(baseURI, originAttributes);
+                          .createCodebasePrincipal(baseURI, {});
   let chromeNav = Services.appShell.createWindowlessBrowser(true);
   let interfaceRequestor = chromeNav.QueryInterface(Ci.nsIInterfaceRequestor);
   let docShell = interfaceRequestor.getInterface(Ci.nsIDocShell);
   docShell.createAboutBlankContentViewer(principal);
 
   return {chromeNav, window: docShell.contentViewer.DOMDocument.defaultView};
 }
 
 add_task(function* test_eventpages() {
+  Service.init();
+
   const {getAPILevelForWindow, getAddonIdForWindow} = ExtensionManagement;
   const {NO_PRIVILEGES, FULL_PRIVILEGES} = ExtensionManagement.API_LEVELS;
   const FAKE_ADDON_ID = "fakeAddonId";
   const OTHER_ADDON_ID = "otherFakeAddonId";
   const EMPTY_ADDON_ID = "";
 
   let fakeAddonId = createWindowWithAddonId(FAKE_ADDON_ID);
   equal(getAddonIdForWindow(fakeAddonId.window), FAKE_ADDON_ID,