Bug 1553371 - Load moz-extension pages in extension principal. r=snorp
authorAgi Sferro <agi@sferro.dev>
Wed, 22 May 2019 19:20:54 +0000
changeset 475029 d4516071aefffa936541f886faefbfba386267ba
parent 475028 7ed7938b37977c38c8737f2f45046d3c3c05da30
child 475030 28d50d22a289aaf16d614fce4026aad2a81e4073
push id36054
push userdvarga@mozilla.com
push dateThu, 23 May 2019 15:52:15 +0000
treeherdermozilla-central@199eaff06ecd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp
bugs1553371
milestone69.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1553371 - Load moz-extension pages in extension principal. r=snorp 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, {