Bug 1017257 - Attach Loop CSP Policy r=sstamm,dmose
authorAdam Roach [:abr] <adam@nostrum.com>
Mon, 06 Oct 2014 15:25:04 -0500
changeset 209326 547ad4574166f3e6f9f7d51558725213df9caf2a
parent 209325 3af0a07adc4940f3ffb0f570ac3834f4986ed050
child 209327 75046b047a6c8dcfb184538cb464c387cc85999c
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewerssstamm, dmose
bugs1017257
milestone35.0a1
Bug 1017257 - Attach Loop CSP Policy r=sstamm,dmose
browser/app/profile/firefox.js
browser/base/content/test/general/browser_devices_get_user_media_about_urls.js
browser/components/loop/run-all-loop-tests.sh
content/base/src/nsDocument.cpp
content/base/src/nsDocument.h
testing/profiles/prefs_general.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1611,16 +1611,21 @@ pref("loop.ringtone", "chrome://browser/
 pref("loop.retry_delay.start", 60000);
 pref("loop.retry_delay.limit", 300000);
 pref("loop.feedback.baseUrl", "https://input.mozilla.org/api/v1/feedback");
 pref("loop.feedback.product", "Loop");
 pref("loop.debug.loglevel", "Error");
 pref("loop.debug.dispatcher", false);
 pref("loop.debug.websocket", false);
 pref("loop.debug.sdk", false);
+#ifdef DEBUG
+pref("loop.CSP", "default-src 'self' about: file: chrome: http://localhost:*; img-src 'self' data: http://www.gravatar.com/ about: file: chrome:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net http://localhost:* ws://localhost:*");
+#else
+pref("loop.CSP", "default-src 'self' about: file: chrome:; img-src 'self' data: http://www.gravatar.com/ about: file: chrome:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net");
+#endif
 pref("loop.oauth.google.redirect_uri", "urn:ietf:wg:oauth:2.0:oob:auto");
 pref("loop.oauth.google.scope", "https://www.google.com/m8/feeds");
 
 // serverURL to be assigned by services team
 pref("services.push.serverURL", "wss://push.services.mozilla.com/");
 
 pref("social.sidebar.unload_timeout_ms", 10000);
 
--- a/browser/base/content/test/general/browser_devices_get_user_media_about_urls.js
+++ b/browser/base/content/test/general/browser_devices_get_user_media_about_urls.js
@@ -7,16 +7,17 @@ const kObservedTopics = [
   "getUserMedia:revoke",
   "getUserMedia:response:deny",
   "getUserMedia:request",
   "recording-device-events",
   "recording-window-ended"
 ];
 
 const PREF_PERMISSION_FAKE = "media.navigator.permission.fake";
+const PREF_LOOP_CSP = "loop.CSP";
 
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService",
                                    "@mozilla.org/mediaManagerService;1",
                                    "nsIMediaManagerService");
 
 var gTab;
@@ -157,30 +158,34 @@ fakeLoopAboutModule.prototype = {
            Ci.nsIAboutModule.ALLOW_SCRIPT |
            Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT;
   }
 };
 
 let factory = XPCOMUtils._getFactory(fakeLoopAboutModule);
 let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
 
+let originalLoopCsp = Services.prefs.getCharPref(PREF_LOOP_CSP);
 registerCleanupFunction(function() {
   gBrowser.removeCurrentTab();
   kObservedTopics.forEach(topic => {
     Services.obs.removeObserver(observer, topic);
   });
   Services.prefs.clearUserPref(PREF_PERMISSION_FAKE);
+  Services.prefs.setCharPref(PREF_LOOP_CSP, originalLoopCsp);
 });
 
 
 let gTests = [
 
 {
   desc: "getUserMedia about:loopconversation shouldn't prompt",
   run: function checkAudioVideoLoop() {
+    Services.prefs.setCharPref(PREF_LOOP_CSP, "default-src 'unsafe-inline'");
+
     let classID = Cc["@mozilla.org/uuid-generator;1"]
                     .getService(Ci.nsIUUIDGenerator).generateUUID();
     registrar.registerFactory(classID, "",
                               "@mozilla.org/network/protocol/about;1?what=loopconversation",
                               factory);
 
     yield loadPage("about:loopconversation");
 
@@ -193,16 +198,17 @@ let gTests = [
     yield promisePopupNotification("webRTC-sharingDevices");
 
     is(getMediaCaptureState(), "CameraAndMicrophone",
        "expected camera and microphone to be shared");
 
     yield closeStream();
 
     registrar.unregisterFactory(classID, factory);
+    Services.prefs.setCharPref(PREF_LOOP_CSP, originalLoopCsp);
   }
 },
 
 {
   desc: "getUserMedia about:evil should prompt",
   run: function checkAudioVideoNonLoop() {
     let classID = Cc["@mozilla.org/uuid-generator;1"]
                     .getService(Ci.nsIUUIDGenerator).generateUUID();
--- a/browser/components/loop/run-all-loop-tests.sh
+++ b/browser/components/loop/run-all-loop-tests.sh
@@ -4,8 +4,13 @@
 set -e
 
 # Main tests
 ./mach xpcshell-test browser/components/loop/
 ./mach marionette-test browser/components/loop/manifest.ini
 
 # The browser_parsable_css.js can fail if we add some css that isn't parsable.
 ./mach mochitest browser/components/loop/test/mochitest browser/base/content/test/general/browser_parsable_css.js
+
+# The check to make sure that the media devices can be used in Loop without
+# prompting is in browser_devices_get_user_media_about_urls.js. It's possible
+# to mess this up with CSP handling, and probably other changes, too.
+./mach mochitest browser/base/content/test/general/browser_devices_get_user_media_about_urls.js
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -2747,16 +2747,43 @@ AppendCSPFromHeader(nsIContentSecurityPo
                 ("CSP refined with policy: \"%s\"",
                 NS_ConvertUTF16toUTF8(policy).get()));
       }
 #endif
   }
   return NS_OK;
 }
 
+bool
+nsDocument::IsLoopDocument(nsIChannel *aChannel)
+{
+  nsCOMPtr<nsIURI> chanURI;
+  nsresult rv = aChannel->GetOriginalURI(getter_AddRefs(chanURI));
+  NS_ENSURE_SUCCESS(rv, false);
+
+  bool isAbout = false;
+  bool isLoop = false;
+  rv = chanURI->SchemeIs("about", &isAbout);
+  NS_ENSURE_SUCCESS(rv, false);
+  if (isAbout) {
+    nsCOMPtr<nsIURI> loopURI;
+    rv = NS_NewURI(getter_AddRefs(loopURI), "about:loopconversation");
+    NS_ENSURE_SUCCESS(rv, false);
+    rv = chanURI->EqualsExceptRef(loopURI, &isLoop);
+    NS_ENSURE_SUCCESS(rv, false);
+    if (!isLoop) {
+      rv = NS_NewURI(getter_AddRefs(loopURI), "about:looppanel");
+      NS_ENSURE_SUCCESS(rv, false);
+      rv = chanURI->EqualsExceptRef(loopURI, &isLoop);
+      NS_ENSURE_SUCCESS(rv, false);
+    }
+  }
+  return isLoop;
+}
+
 nsresult
 nsDocument::InitCSP(nsIChannel* aChannel)
 {
   nsCOMPtr<nsIContentSecurityPolicy> csp;
   if (!CSPService::sCSPEnabled) {
 #ifdef PR_LOGGING
     PR_LOG(gCspPRLog, PR_LOG_DEBUG,
            ("CSP is disabled, skipping CSP init for document %p", this));
@@ -2800,19 +2827,23 @@ nsDocument::InitCSP(nsIChannel* aChannel
         appsService->GetDefaultCSPByLocalId(appId, appDefaultCSP);
         if (!appDefaultCSP.IsEmpty()) {
           applyAppDefaultCSP = true;
         }
       }
     }
   }
 
+ // Check if this is part of the Loop/Hello service
+ bool applyLoopCSP = IsLoopDocument(aChannel);
+
   // If there's no CSP to apply, go ahead and return early
   if (!applyAppDefaultCSP &&
       !applyAppManifestCSP &&
+      !applyLoopCSP &&
       cspHeaderValue.IsEmpty() &&
       cspROHeaderValue.IsEmpty()) {
 #ifdef PR_LOGGING
     nsCOMPtr<nsIURI> chanURI;
     aChannel->GetURI(getter_AddRefs(chanURI));
     nsAutoCString aspec;
     chanURI->GetAsciiSpec(aspec);
     PR_LOG(gCspPRLog, PR_LOG_DEBUG,
@@ -2875,16 +2906,27 @@ nsDocument::InitCSP(nsIChannel* aChannel
     csp->AppendPolicy(appDefaultCSP, false);
   }
 
   // ----- if the doc is an app and specifies a CSP in its manifest, apply it.
   if (applyAppManifestCSP) {
     csp->AppendPolicy(appManifestCSP, false);
   }
 
+  // ----- if the doc is part of Loop, apply the loop CSP
+  if (applyLoopCSP) {
+    nsAdoptingString loopCSP;
+    loopCSP = Preferences::GetString("loop.CSP");
+    NS_ASSERTION(loopCSP, "Missing loop.CSP preference");
+    // If the pref has been removed, we continue without setting a CSP
+    if (loopCSP) {
+      csp->AppendPolicy(loopCSP, false);
+    }
+  }
+
   // ----- if there's a full-strength CSP header, apply it.
   if (!cspHeaderValue.IsEmpty()) {
     rv = AppendCSPFromHeader(csp, cspHeaderValue, false);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // ----- if there's a report-only CSP header, apply it.
   if (!cspROHeaderValue.IsEmpty()) {
--- a/content/base/src/nsDocument.h
+++ b/content/base/src/nsDocument.h
@@ -1657,16 +1657,17 @@ private:
   mozilla::dom::VisibilityState GetVisibilityState() const;
   void NotifyStyleSheetAdded(nsIStyleSheet* aSheet, bool aDocumentSheet);
   void NotifyStyleSheetRemoved(nsIStyleSheet* aSheet, bool aDocumentSheet);
 
   void PostUnblockOnloadEvent();
   void DoUnblockOnload();
 
   nsresult CheckFrameOptions();
+  bool IsLoopDocument(nsIChannel* aChannel);
   nsresult InitCSP(nsIChannel* aChannel);
 
   void FlushCSPWebConsoleErrorQueue()
   {
     mCSPWebConsoleErrorQueue.Flush(this);
   }
 
   /**
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -249,16 +249,17 @@ user_pref("dom.mozApps.debug", true);
 user_pref("browser.newtabpage.directory.source", 'data:application/json,{"testing":1}');
 user_pref("browser.newtabpage.directory.ping", "");
 
 // Enable Loop
 user_pref("loop.enabled", true);
 user_pref("loop.throttled", false);
 user_pref("loop.oauth.google.URL", "http://%(server)s/browser/browser/components/loop/test/mochitest/google_service.sjs?action=");
 user_pref("loop.oauth.google.getContactsURL", "http://%(server)s/browser/browser/components/loop/test/mochitest/google_service.sjs?action=contacts");
+user_pref("loop.CSP","default-src 'self' about: file: chrome: data: wss://* http://* https://*");
 
 // Ensure UITour won't hit the network
 user_pref("browser.uitour.pinnedTabUrl", "http://%(server)s/uitour-dummy/pinnedTab");
 user_pref("browser.uitour.url", "http://%(server)s/uitour-dummy/tour");
 
 user_pref("media.eme.enabled", true);
 
 // Don't prompt about e10s