Bug 805322 - Device Storage - Add tests to ensure Application Permissions match that of the Permission Matrix. r=jmaher, a=blocking-basecamp
authorDoug Turner <dougt@dougt.org>
Wed, 31 Oct 2012 10:06:51 -0700
changeset 114016 261667b3cbf586aa69f1ce1d125f090ac51e3f49
parent 114015 eab0a3d65b1e1d68d78930ebdfb381179c57d7af
child 114017 5af88988ae053004951760bdca7ba4bd59504aa2
push idunknown
push userunknown
push dateunknown
reviewersjmaher, blocking-basecamp
bugs805322
milestone18.0a2
Bug 805322 - Device Storage - Add tests to ensure Application Permissions match that of the Permission Matrix. r=jmaher, a=blocking-basecamp
dom/devicestorage/test/Makefile.in
dom/devicestorage/test/test_app_permissions.html
testing/marionette/jar.mn
testing/specialpowers/content/MockPermissionPrompt.jsm
testing/specialpowers/content/specialpowersAPI.js
testing/specialpowers/jar.mn
--- a/dom/devicestorage/test/Makefile.in
+++ b/dom/devicestorage/test/Makefile.in
@@ -28,9 +28,13 @@ MOCHITEST_FILES	= \
 		test_enumerateOptions.html \
 		test_lastModificationFilter.html \
 		test_stat.html \
 		test_watch.html \
 		test_watchOther.html \
 		devicestorage_common.js \
 		$(NULL)
 
+MOCHITEST_CHROME_FILES =\
+		test_app_permissions.html \
+		$(NULL)
+
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/devicestorage/test/test_app_permissions.html
@@ -0,0 +1,625 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=805322
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Permission test for Device Storage</title>
+  <script type="application/javascript"
+           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css"
+         href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+</head>
+<body>
+<a target="_blank"
+    href="https://bugzilla.mozilla.org/show_bug.cgi?id=805322">Mozilla Bug 805322</a>
+<p id="display"></p>
+<div id="content">
+  
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+
+function randomFilename(l) {
+  var set = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZ";
+  var result = "";
+  for (var i=0; i<l; i++) {
+    var r = Math.floor(set.length * Math.random());
+    result += set.substring(r, r + 1);
+  }
+  return result;
+}
+
+var MockPermissionPrompt = SpecialPowers.MockPermissionPrompt;
+MockPermissionPrompt.init();
+
+SimpleTest.waitForExplicitFinish();
+
+function TestAdd(iframe, data) {
+
+  var storage = iframe.contentDocument.defaultView.navigator.getDeviceStorage(data.type);
+  isnot(storage, null, "Should be able to get storage object for " + data.type);
+
+  var blob = new Blob(["Kyle Huey is not a helicopter."], {type: data.mimeType});
+
+  request = storage.addNamed(blob, randomFilename(100) + "hi" + data.fileExtension);
+  isnot(request, null, "Should be able to get request");
+
+  request.onsuccess = function() {
+    is(data.shouldPass, true, "onsuccess was called for type " + data.type);
+    testComplete(iframe, data);
+  };
+
+  request.onerror = function(e) {
+    isnot(data.shouldPass, true, "onfailure was called for type " + data.type + " Error: " + e.target.error.name);
+    is(e.target.error.name, "SecurityError", "onerror should fire a SecurityError");
+    testComplete(iframe, data);
+  };
+}
+
+function TestGet(iframe, data) {
+
+  createTestFile();
+
+  var storage = iframe.contentDocument.defaultView.navigator.getDeviceStorage(data.type);
+  isnot(storage, null, "Should be able to get storage object for " + data.type);
+
+  request = storage.get("testfile");
+  isnot(request, null, "Should be able to get request");
+
+  request.onsuccess = function() {
+    is(data.shouldPass, true, "onsuccess was called for type " + data.type);
+    testComplete(iframe, data);
+  };
+
+  request.onerror = function(e) {
+    isnot(data.shouldPass, true, "onfailure was called for type " + data.type + " Error: " + e.target.error.name);
+    testComplete(iframe, data);
+  };
+}
+
+function TestDelete(iframe, data) {
+
+  createTestFile();
+
+  var storage = iframe.contentDocument.defaultView.navigator.getDeviceStorage(data.type);
+  isnot(storage, null, "Should be able to get storage object for " + data.type);
+
+  request = storage.delete("testfile");
+  isnot(request, null, "Should be able to get request");
+
+  request.onsuccess = function() {
+    is(data.shouldPass, true, "onsuccess was called for type " + data.type);
+    testComplete(iframe, data);
+  };
+
+  request.onerror = function(e) {
+    isnot(data.shouldPass, true, "onfailure was called for type " + data.type + " Error: " + e.target.error.name);
+    is(e.target.error.name, "SecurityError", "onerror should fire a SecurityError");
+    testComplete(iframe, data);
+  };
+}
+
+function TestEnumerate(iframe, data) {
+
+  createTestFile();
+
+  var storage = iframe.contentDocument.defaultView.navigator.getDeviceStorage(data.type);
+  isnot(storage, null, "Should be able to get storage object for " + data.type);
+
+  request = storage.enumerate();
+  isnot(request, null, "Should be able to get request");
+
+  request.onsuccess = function(e) {
+    is(data.shouldPass, true, "onsuccess was called for type " + data.type);
+
+    if (e.target.result == null) {
+      testComplete(iframe, data);
+      return;
+    }
+    e.target.continue();
+  };
+
+  request.onerror = function(e) {
+    isnot(data.shouldPass, true, "onfailure was called for type " + data.type + " Error: " + e.target.error.name);
+    is(e.target.error.name, "SecurityError", "onerror should fire a SecurityError");
+    testComplete(iframe, data);
+  };
+}
+
+var gTestUri = "https://example.com/tests/dom/devicestorage/test/test_app_permissions.html"
+
+var gData = [
+
+  // Get
+  // Web applications with no permissions
+  {
+    type: 'pictures',
+    shouldPass: false,
+    test: TestGet
+  },
+  {
+    type: 'videos',
+    shouldPass: false,
+    test: TestGet
+  },
+  {
+    type: 'music',
+    shouldPass: false,
+    test: TestGet
+  },
+  {
+    type: 'sdcard',
+    shouldPass: false,
+    test: TestGet
+  },
+
+  // Web applications with permission granted
+  {
+    type: 'pictures',
+    shouldPass: true,
+
+    permissions: ["device-storage:pictures"],
+
+    test: TestGet
+  },
+  {
+    type: 'videos',
+    shouldPass: true,
+
+    permissions: ["device-storage:videos"],
+
+    test: TestGet
+  },
+  {
+    type: 'music',
+    shouldPass: true,
+
+    permissions: ["device-storage:music"],
+
+    test: TestGet
+  },
+  {
+    type: 'sdcard',
+    shouldPass: true,
+
+    permissions: ["device-storage:sdcard"],
+
+    test: TestGet
+  },
+
+  // Certified application with permision granted
+  {
+    type: 'pictures',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:pictures"],
+
+    test: TestGet
+  },
+  {
+    type: 'videos',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:videos"],
+
+    test: TestGet
+  },
+  {
+    type: 'music',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:music"],
+
+    test: TestGet
+  },
+  {
+    type: 'sdcard',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:sdcard"],
+
+    test: TestGet
+  },
+
+
+  // Add
+
+
+  // Web applications with no permissions
+  {
+    type: 'pictures',
+    mimeType: 'image/png',
+    fileExtension: '.png',
+    shouldPass: false,
+    test: TestAdd
+  },
+  {
+    type: 'videos',
+    mimeType: 'video/ogm',
+    fileExtension: '.ogm',
+    shouldPass: false,
+    test: TestAdd
+  },
+  {
+    type: 'music',
+    mimeType: 'audio/ogg',
+    fileExtension: '.ogg',
+    shouldPass: false,
+    test: TestAdd
+  },
+  {
+    type: 'sdcard',
+    mimeType: 'text/plain',
+    fileExtension: '.txt',
+    shouldPass: false,
+    test: TestAdd
+  },
+
+  // Web applications with permission granted
+  {
+    type: 'pictures',
+    mimeType: 'image/png',
+    fileExtension: '.png',
+    shouldPass: true,
+
+    permissions: ["device-storage:pictures"],
+
+    test: TestAdd
+  },
+  {
+    type: 'videos',
+    mimeType: 'video/ogm',
+    fileExtension: '.ogm',
+    shouldPass: true,
+
+    permissions: ["device-storage:videos"],
+
+    test: TestAdd
+  },
+  {
+    type: 'music',
+    mimeType: 'audio/ogg',
+    fileExtension: '.ogg',
+    shouldPass: true,
+
+    permissions: ["device-storage:music"],
+
+    test: TestAdd
+  },
+  {
+    type: 'sdcard',
+    mimeType: 'text/plain',
+    fileExtension: '.txt',
+    shouldPass: true,
+
+    permissions: ["device-storage:sdcard"],
+
+    test: TestAdd
+  },
+
+  // Certified application with permision granted
+  {
+    type: 'pictures',
+    mimeType: 'image/png',
+    fileExtension: '.png',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:pictures"],
+
+    test: TestAdd
+  },
+  {
+    type: 'videos',
+    mimeType: 'video/ogm',
+    fileExtension: '.ogm',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:videos"],
+
+    test: TestAdd
+  },
+  {
+    type: 'music',
+    mimeType: 'audio/ogg',
+    fileExtension: '.ogg',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:music"],
+
+    test: TestAdd
+  },
+  {
+    type: 'sdcard',
+    mimeType: 'text/plain',
+    fileExtension: '.txt',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:sdcard"],
+
+    test: TestAdd
+  },
+
+
+// Delete 
+
+  // Web applications with no permissions
+  {
+    type: 'pictures',
+    shouldPass: false,
+    test: TestDelete
+  },
+  {
+    type: 'videos',
+    shouldPass: false,
+    test: TestDelete
+  },
+  {
+    type: 'music',
+    shouldPass: false,
+    test: TestDelete
+  },
+  {
+    type: 'sdcard',
+    shouldPass: false,
+    test: TestDelete
+  },
+
+  // Web applications with permission granted
+  {
+    type: 'pictures',
+    shouldPass: true,
+
+    permissions: ["device-storage:pictures"],
+
+    test: TestDelete
+  },
+  {
+    type: 'videos',
+    shouldPass: true,
+
+    permissions: ["device-storage:videos"],
+
+    test: TestDelete
+  },
+  {
+    type: 'music',
+    shouldPass: true,
+
+    permissions: ["device-storage:music"],
+
+    test: TestDelete
+  },
+  {
+    type: 'sdcard',
+    shouldPass: true,
+
+    permissions: ["device-storage:sdcard"],
+
+    test: TestDelete
+  },
+
+  // Certified application with permision granted
+  {
+    type: 'pictures',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:pictures"],
+
+    test: TestDelete
+  },
+  {
+    type: 'videos',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:videos"],
+
+    test: TestDelete
+  },
+  {
+    type: 'music',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:music"],
+
+    test: TestDelete
+  },
+  {
+    type: 'sdcard',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:sdcard"],
+
+    test: TestDelete
+  },
+
+// Enumeration 
+
+  // Web applications with no permissions
+  {
+    type: 'pictures',
+    shouldPass: false,
+    test: TestEnumerate
+  },
+  {
+    type: 'videos',
+    shouldPass: false,
+    test: TestEnumerate
+  },
+  {
+    type: 'music',
+    shouldPass: false,
+    test: TestEnumerate
+  },
+  {
+    type: 'sdcard',
+    shouldPass: false,
+    test: TestEnumerate
+  },
+
+  // Web applications with permission granted
+  {
+    type: 'pictures',
+    shouldPass: true,
+
+    permissions: ["device-storage:pictures"],
+
+    test: TestEnumerate
+  },
+  {
+    type: 'videos',
+    shouldPass: true,
+
+    permissions: ["device-storage:videos"],
+
+    test: TestEnumerate
+  },
+  {
+    type: 'music',
+    shouldPass: true,
+
+    permissions: ["device-storage:music"],
+
+    test: TestEnumerate
+  },
+  {
+    type: 'sdcard',
+    shouldPass: true,
+
+    permissions: ["device-storage:sdcard"],
+
+    test: TestEnumerate
+  },
+
+  // Certified application with permision granted
+  {
+    type: 'pictures',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:pictures"],
+
+    test: TestEnumerate
+  },
+  {
+    type: 'videos',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:videos"],
+
+    test: TestEnumerate
+  },
+  {
+    type: 'music',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:music"],
+
+    test: TestEnumerate
+  },
+  {
+    type: 'sdcard',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:sdcard"],
+
+    test: TestEnumerate
+  },
+
+];
+
+function setupTest(iframe,data) {
+  if (data.permissions) {
+    for (var j in data.permissions) {
+      SpecialPowers.addPermission(data.permissions[j], true, iframe.contentDocument);
+    }
+  }
+}
+
+function testComplete(iframe, data) {
+  if (data.permissions) {
+    for (var j in data.permissions) {
+      SpecialPowers.removePermission(data.permissions[j], iframe.contentDocument);
+    }
+  }
+  
+  document.getElementById('content').removeChild(iframe);
+
+  if (gData.length == 0) {
+    SimpleTest.finish();
+  } else {
+    gTestRunner.next();
+  }
+}
+
+function runTest() {
+  while (gData.length > 0) {
+    var iframe = document.createElement('iframe');
+    var data = gData.pop();
+
+    iframe.setAttribute('mozbrowser', '');
+    if (data.app) {
+      iframe.setAttribute('mozapp', data.app);
+    }
+
+    iframe.src = gTestUri;
+
+    iframe.addEventListener('load', function(e) {
+      setupTest(iframe, data)
+      data.test(iframe, data);
+    });
+
+    document.getElementById('content').appendChild(iframe);
+    yield;
+  }
+}
+
+function createTestFile() {
+try {
+  const Cc = SpecialPowers.Cc;
+  const Ci = SpecialPowers.Ci;
+  var directoryService = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
+  var f = directoryService.get("TmpD", Ci.nsIFile);
+  f.appendRelativePath("device-storage-testing");
+  f.remove(true);
+  f.appendRelativePath("testfile");
+  f.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0666);
+  } catch(e) {}
+}
+
+createTestFile();
+var gTestRunner = runTest();
+SpecialPowers.addPermission("browser", true, gTestUri);
+
+// We are more permissive with CSP in our testing environment....
+const DEFAULT_CSP_PRIV = "default-src *; script-src 'self'; style-src 'self' 'unsafe-inline'; object-src 'none'";
+const DEFAULT_CSP_CERT = "default-src *; script-src 'self'; style-src 'self'; object-src 'none'";
+
+SpecialPowers.pushPrefEnv({'set': [["dom.mozBrowserFramesEnabled", true],
+                                   ["device.storage.enabled", true],
+                                   ["device.storage.testing", true],
+                                   ["device.storage.prompt.testing", false],
+                                   ["security.apps.privileged.CSP.default", DEFAULT_CSP_PRIV],
+                                   ["security.apps.certified.CSP.default", DEFAULT_CSP_CERT]]},
+  function() {  gTestRunner.next(); });
+
+</script>
+</pre>
+</body>
+</html>
--- a/testing/marionette/jar.mn
+++ b/testing/marionette/jar.mn
@@ -24,8 +24,9 @@ marionette.jar:
   content/SpecialPowersObserver.js (../specialpowers/components/SpecialPowersObserver.js)
   content/specialpowersAPI.js (../specialpowers/content/specialpowersAPI.js)
   content/SpecialPowersObserverAPI.js (../specialpowers/content/SpecialPowersObserverAPI.js)
   content/ChromePowers.js (../mochitest/tests/SimpleTest/ChromePowers.js)
   content/MozillaLogger.js (../specialpowers/content/MozillaLogger.js)
 
 % resource specialpowers %modules/
   modules/MockFilePicker.jsm (../specialpowers/content/MockFilePicker.jsm)
+  modules/MockPermissionPrompt.jsm (../specialpowers/content/MockPermissionPrompt.jsm)
new file mode 100644
--- /dev/null
+++ b/testing/specialpowers/content/MockPermissionPrompt.jsm
@@ -0,0 +1,85 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var EXPORTED_SYMBOLS = ["MockPermissionPrompt"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cm = Components.manager;
+const Cu = Components.utils;
+
+const CONTRACT_ID = "@mozilla.org/content-permission/prompt;1";
+
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+var oldClassID, oldFactory;
+var newClassID = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID();
+var newFactory = {
+  createInstance: function(aOuter, aIID) {
+    if (aOuter)
+      throw Components.results.NS_ERROR_NO_AGGREGATION;
+    return new MockPermissionPromptInstance().QueryInterface(aIID);
+  },
+  lockFactory: function(aLock) {
+    throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+  },
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
+};
+
+var MockPermissionPrompt = {
+  init: function() {
+    this.reset();
+    if (!registrar.isCIDRegistered(newClassID)) {
+      oldClassID = registrar.contractIDToCID(CONTRACT_ID);
+      oldFactory = Cm.getClassObject(Cc[CONTRACT_ID], Ci.nsIFactory);
+      registrar.unregisterFactory(oldClassID, oldFactory);
+      registrar.registerFactory(newClassID, "", CONTRACT_ID, newFactory);
+    }
+  },
+  
+  reset: function() {
+  },
+  
+  cleanup: function() {
+    this.reset();
+    if (oldFactory) {
+      registrar.unregisterFactory(newClassID, newFactory);
+      registrar.registerFactory(oldClassID, "", CONTRACT_ID, oldFactory);
+    }
+  },
+};
+
+function MockPermissionPromptInstance() { };
+MockPermissionPromptInstance.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionPrompt]),
+
+  promptResult: Ci.nsIPermissionManager.UNKNOWN_ACTION,
+
+  prompt: function(request) {
+
+    this.promptResult = Services.perms.testExactPermissionFromPrincipal(request.principal,
+                                                                        request.type);
+    if (this.promptResult == Ci.nsIPermissionManager.ALLOW_ACTION) {
+      request.allow();
+    }
+    else {
+      request.cancel();
+    }
+  }
+};
+
+// Expose everything to content. We call reset() here so that all of the relevant
+// lazy expandos get added.
+MockPermissionPrompt.reset();
+function exposeAll(obj) {
+  var props = {};
+  for (var prop in obj)
+    props[prop] = 'rw';
+  obj.__exposedProps__ = props;
+}
+exposeAll(MockPermissionPrompt);
+exposeAll(MockPermissionPromptInstance.prototype);
--- a/testing/specialpowers/content/specialpowersAPI.js
+++ b/testing/specialpowers/content/specialpowersAPI.js
@@ -5,16 +5,17 @@
  * order to be used as a replacement for UniversalXPConnect
  */
 
 var Ci = Components.interfaces;
 var Cc = Components.classes;
 var Cu = Components.utils;
 
 Components.utils.import("resource://specialpowers/MockFilePicker.jsm");
+Components.utils.import("resource://specialpowers/MockPermissionPrompt.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 function SpecialPowersAPI() { 
   this._consoleListeners = [];
   this._encounteredCrashDumpFiles = [];
   this._unexpectedCrashDumpFiles = { };
   this._crashDumpDir = null;
   this._mfl = null;
@@ -410,16 +411,20 @@ SpecialPowersAPI.prototype = {
   wrap: function(obj) { return isWrapper(obj) ? obj : wrapPrivileged(obj); },
   unwrap: unwrapIfWrapped,
   isWrapper: isWrapper,
 
   get MockFilePicker() {
     return MockFilePicker
   },
 
+  get MockPermissionPrompt() {
+    return MockPermissionPrompt
+  },
+
   get Services() {
     return wrapPrivileged(Services);
   },
 
   /*
    * Convenient shortcuts to the standard Components abbreviations. Note that
    * we don't SpecialPowers-wrap Components.interfaces, because it's available
    * to untrusted content, and wrapping it confuses QI and identity checks.
--- a/testing/specialpowers/jar.mn
+++ b/testing/specialpowers/jar.mn
@@ -2,8 +2,9 @@ specialpowers.jar:
 % content specialpowers %content/
   content/specialpowers.js (content/specialpowers.js)
   content/specialpowersAPI.js (content/specialpowersAPI.js)
   content/SpecialPowersObserverAPI.js (content/SpecialPowersObserverAPI.js)
   content/MozillaLogger.js (content/MozillaLogger.js)
 
 % resource specialpowers %modules/
   modules/MockFilePicker.jsm (content/MockFilePicker.jsm)
+  modules/MockPermissionPrompt.jsm (content/MockPermissionPrompt.jsm)