Bug 1553371 - Load moz-extension pages in extension principal. r=snorp a=jcristau
authorAgi Sferro <agi@sferro.dev>
Wed, 22 May 2019 19:20:54 +0000
changeset 536488 c6df1fc8a2a17b2bbbf1c2dca6a391d0df67f449
parent 536487 fb23d87ad492d731511d436db7e446afea90b4ef
child 536489 8a9ead04803465749d2cb41a9ca00cc72a050815
push id2082
push userffxbld-merge
push dateMon, 01 Jul 2019 08:34:18 +0000
treeherdermozilla-release@2fb19d0466d2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp, jcristau
bugs1553371
milestone68.0
Bug 1553371 - Load moz-extension pages in extension principal. r=snorp a=jcristau WebExtension can always open their respective WebExtension pages even when the WebExtension page is not content accessible. However, this is not true for `tabs.update`, which couldn't link to WebExtension pages at all. Similarly, a user should be able to open a WebExtension page directly by typing the URL. To fix the above problems we pass the correct `triggeringPrincipal` when loading such URIs. This change also makes URI typed by the user not use the `systemPrincipal` anymore but a more restrictive codebase principal local to the page that's being typed to avoid unintended side-effects. This also makes the triggering URI always the page for these privileged pages, so we need to adjust some tests to account for that by loading unprivileged `http` pages instead. Differential Revision: https://phabricator.services.mozilla.com/D32149
mobile/android/components/extensions/ext-tabs.js
mobile/android/geckoview/src/androidTest/assets/web_extensions/extension-page-update/manifest.json
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
mobile/android/modules/geckoview/GeckoViewNavigation.jsm
--- a/mobile/android/components/extensions/ext-tabs.js
+++ b/mobile/android/components/extensions/ext-tabs.js
@@ -325,17 +325,20 @@ this.tabs = class extends ExtensionAPI {
 
           if (updateProperties.url !== null) {
             let url = context.uri.resolve(updateProperties.url);
 
             if (!context.checkLoadURL(url, {dontReportErrors: true})) {
               return Promise.reject({message: `Illegal URL: ${url}`});
             }
 
-            nativeTab.browser.loadURI(url);
+            let options = {
+              triggeringPrincipal: context.principal,
+            };
+            nativeTab.browser.loadURI(url, options);
           }
 
           if (updateProperties.active !== null) {
             if (updateProperties.active) {
               BrowserApp.selectTab(nativeTab);
             } else {
               // Not sure what to do here? Which tab should we select?
             }
--- a/mobile/android/geckoview/src/androidTest/assets/web_extensions/extension-page-update/manifest.json
+++ b/mobile/android/geckoview/src/androidTest/assets/web_extensions/extension-page-update/manifest.json
@@ -7,18 +7,15 @@
   },
   "content_scripts": [
     {
       "matches": ["*://*.example.com/*"],
       "js": ["tabs.js"],
       "run_at": "document_idle"
     }
   ],
-  "web_accessible_resources": [
-    "tab.html"
-  ],
   "permissions": [
     "geckoViewAddons",
     "nativeMessaging",
     "tabs",
     "<all_urls>"
   ]
 }
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
@@ -1,14 +1,17 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.geckoview.test
 
+import android.os.Handler
+import android.os.Looper
+import android.support.test.InstrumentationRegistry
 import org.mozilla.geckoview.AllowOrDeny
 import org.mozilla.geckoview.ContentBlocking
 import org.mozilla.geckoview.GeckoResult
 import org.mozilla.geckoview.GeckoSession
 import org.mozilla.geckoview.GeckoSession.NavigationDelegate.LoadRequest
 import org.mozilla.geckoview.GeckoSessionSettings
 import org.mozilla.geckoview.WebRequestError
 
@@ -18,23 +21,43 @@ import org.mozilla.geckoview.test.rule.G
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.Setting
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
 import org.mozilla.geckoview.test.util.Callbacks
 
 import android.support.test.filters.MediumTest
 import android.support.test.runner.AndroidJUnit4
 import org.hamcrest.Matchers.*
+import org.junit.After
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mozilla.geckoview.test.util.HttpBin
+import java.net.URI
 
 @RunWith(AndroidJUnit4::class)
 @MediumTest
 @ReuseSession(false)
 class NavigationDelegateTest : BaseSessionTest() {
+    companion object {
+        val TEST_ENDPOINT: String = "http://localhost:4242"
+    }
+
+    lateinit var server: HttpBin
+
+    @Before
+    fun setup() {
+        server = HttpBin(InstrumentationRegistry.getTargetContext(), URI.create(TEST_ENDPOINT))
+        server.start()
+    }
+
+    @After
+    fun cleanup() {
+        server.stop()
+    }
 
     fun testLoadErrorWithErrorPage(testUri: String, expectedCategory: Int,
                                    expectedError: Int,
                                    errorPageUrl: String?) {
         sessionRule.delegateDuringNextWait(
                 object : Callbacks.ProgressDelegate, Callbacks.NavigationDelegate, Callbacks.ContentDelegate {
                     @AssertCalled(count = 1, order = [1])
                     override fun onLoadRequest(session: GeckoSession,
@@ -574,17 +597,17 @@ class NavigationDelegateTest : BaseSessi
 
         if (sessionRule.env.isMultiprocess) {
             assertThat("Snapshots should not be null",
                        result?.get("content"), notNullValue())
         }
     }
 
     @Test fun load() {
-        sessionRule.session.loadTestPath(HELLO_HTML_PATH)
+        sessionRule.session.loadUri("$TEST_ENDPOINT$HELLO_HTML_PATH")
         sessionRule.waitForPageStop()
 
         sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1, order = [1])
             override fun onLoadRequest(session: GeckoSession,
                                        request: LoadRequest):
                                        GeckoResult<AllowOrDeny>? {
                 assertThat("Session should not be null", session, notNullValue())
@@ -768,17 +791,17 @@ class NavigationDelegateTest : BaseSessi
         loadDataHelper("/assets/www/images/test.gif", "image/gif")
     }
 
     @Test fun loadData_noMimeType() {
         loadDataHelper("/assets/www/images/test.gif")
     }
 
     @Test fun reload() {
-        sessionRule.session.loadTestPath(HELLO_HTML_PATH)
+        sessionRule.session.loadUri("$TEST_ENDPOINT$HELLO_HTML_PATH")
         sessionRule.waitForPageStop()
 
         sessionRule.session.reload()
         sessionRule.waitForPageStop()
 
         sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1, order = [1])
             override fun onLoadRequest(session: GeckoSession,
@@ -810,20 +833,20 @@ class NavigationDelegateTest : BaseSessi
             @AssertCalled(false)
             override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession>? {
                 return null
             }
         })
     }
 
     @Test fun goBackAndForward() {
-        sessionRule.session.loadTestPath(HELLO_HTML_PATH)
+        sessionRule.session.loadUri("$TEST_ENDPOINT$HELLO_HTML_PATH")
         sessionRule.waitForPageStop()
 
-        sessionRule.session.loadTestPath(HELLO2_HTML_PATH)
+        sessionRule.session.loadUri("$TEST_ENDPOINT$HELLO2_HTML_PATH")
         sessionRule.waitForPageStop()
 
         sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1)
             override fun onLocationChange(session: GeckoSession, url: String?) {
                 assertThat("URL should match", url, endsWith(HELLO2_HTML_PATH))
             }
         })
--- a/mobile/android/modules/geckoview/GeckoViewNavigation.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewNavigation.jsm
@@ -110,19 +110,21 @@ class GeckoViewNavigation extends GeckoV
           this.moduleManager.updateRemoteTypeForURI(uri);
         }
 
         let parsedUri;
         let triggeringPrincipal;
         try {
           parsedUri = Services.io.newURI(uri);
           if (parsedUri.schemeIs("about") || parsedUri.schemeIs("data") ||
-              parsedUri.schemeIs("file") || parsedUri.schemeIs("resource")) {
+              parsedUri.schemeIs("file") || parsedUri.schemeIs("resource") ||
+              parsedUri.schemeIs("moz-extension")) {
             // Only allow privileged loading for certain URIs.
-            triggeringPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+            triggeringPrincipal = Services.scriptSecurityManager
+                .createCodebasePrincipal(parsedUri, {});
           }
         } catch (ignored) {
         }
         if (!triggeringPrincipal) {
           triggeringPrincipal = Services.scriptSecurityManager.createNullPrincipal({});
         }
 
         this.browser.loadURI(parsedUri ? parsedUri.spec : uri, {