Bug 942639 - Make DataStore API Certified-only for 1.3, r=ehsan, r=fabrice

This commit is contained in:
Andrea Marchesini 2013-12-05 23:12:23 +00:00
parent 6443657acb
commit 97c72a5e48
25 changed files with 307 additions and 38 deletions

View File

@ -383,12 +383,12 @@ interface nsIFrameScriptLoader : nsISupports
nsIDOMDOMStringList getDelayedFrameScripts();
};
[scriptable, builtinclass, uuid(b37821ff-df79-44d4-821c-6d6ec4dfe1e9)]
[scriptable, builtinclass, uuid(ad57800b-ff21-4e2f-91d3-e68615ae8afe)]
interface nsIProcessChecker : nsISupports
{
/**
* Return true iff the "remote" process has |aPermission|. This is
* Return true if the "remote" process has |aPermission|. This is
* intended to be used by JS implementations of cross-process DOM
* APIs, like so
*
@ -409,7 +409,7 @@ interface nsIProcessChecker : nsISupports
boolean assertPermission(in DOMString aPermission);
/**
* Return true iff the "remote" process has |aManifestURL|. This is
* Return true if the "remote" process has |aManifestURL|. This is
* intended to be used by JS implementations of cross-process DOM
* APIs, like so
*
@ -432,14 +432,17 @@ interface nsIProcessChecker : nsISupports
boolean assertAppHasPermission(in DOMString aPermission);
/**
* Return true iff the "remote" process' principal has an appStatus equal to
* Return true if the "remote" process' principal has an appStatus equal to
* |aStatus|.
*
* This interface only returns meaningful data when our content is
* in a separate process. If it shares the same OS process as us,
* then applying this permission check doesn't add any security,
* though it doesn't hurt anything either.
*
* Note: If the remote content process does *not* has the |aStatus|,
* it will be killed as a precaution.
*/
boolean checkAppHasStatus(in unsigned short aStatus);
boolean assertAppHasStatus(in unsigned short aStatus);
};

View File

@ -805,8 +805,8 @@ nsFrameMessageManager::AssertAppHasPermission(const nsAString& aPermission,
}
NS_IMETHODIMP
nsFrameMessageManager::CheckAppHasStatus(unsigned short aStatus,
bool* aHasStatus)
nsFrameMessageManager::AssertAppHasStatus(unsigned short aStatus,
bool* aHasStatus)
{
*aHasStatus = false;
@ -1592,6 +1592,12 @@ public:
// In a single-process scenario, the child always has all capabilities.
return true;
}
virtual bool CheckAppHasStatus(unsigned short aStatus)
{
// In a single-process scenario, the child always has all capabilities.
return true;
}
};

View File

@ -313,10 +313,9 @@ this.DOMApplicationRegistry = {
// Create or Update the DataStore for this app
this._readManifests([{ id: aId }], (function(aResult) {
this.updateDataStore(this.webapps[aId].localId,
this.webapps[aId].origin,
this.webapps[aId].manifestURL,
aResult[0].manifest);
let app = this.webapps[aId];
this.updateDataStore(app.localId, app.origin, app.manifestURL,
aResult[0].manifest, app.appStatus);
}).bind(this));
},
@ -588,7 +587,15 @@ this.DOMApplicationRegistry = {
}).bind(this));
},
updateDataStore: function(aId, aOrigin, aManifestURL, aManifest) {
updateDataStore: function(aId, aOrigin, aManifestURL, aManifest, aAppStatus) {
// Just Certified Apps can use DataStores
let prefName = "dom.testing.datastore_enabled_for_hosted_apps";
if (aAppStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED &&
(Services.prefs.getPrefType(prefName) == Services.prefs.PREF_INVALID ||
!Services.prefs.getBoolPref(prefName))) {
return;
}
if ('datastores-owned' in aManifest) {
for (let name in aManifest['datastores-owned']) {
let readonly = "access" in aManifest['datastores-owned'][name]
@ -1477,7 +1484,7 @@ this.DOMApplicationRegistry = {
true);
}
this.updateDataStore(this.webapps[id].localId, app.origin,
app.manifestURL, aData);
app.manifestURL, aData, app.appStatus);
this.broadcastMessage("Webapps:UpdateState", {
app: app,
manifest: aData,
@ -1651,7 +1658,7 @@ this.DOMApplicationRegistry = {
}
this.updateDataStore(this.webapps[id].localId, app.origin,
app.manifestURL, app.manifest);
app.manifestURL, app.manifest, app.appStatus);
app.name = manifest.name;
app.csp = manifest.csp || "";
@ -2287,7 +2294,8 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
}
this.updateDataStore(this.webapps[id].localId, this.webapps[id].origin,
this.webapps[id].manifestURL, jsonManifest);
this.webapps[id].manifestURL, jsonManifest,
this.webapps[id].appStatus);
}
for each (let prop in ["installState", "downloadAvailable", "downloading",
@ -2398,7 +2406,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
}
this.updateDataStore(this.webapps[aId].localId, aNewApp.origin,
aNewApp.manifestURL, aManifest);
aNewApp.manifestURL, aManifest, aNewApp.appStatus);
this.broadcastMessage("Webapps:UpdateState", {
app: app,

View File

@ -50,6 +50,8 @@
#include "TimeManager.h"
#include "DeviceStorage.h"
#include "nsIDOMNavigatorSystemMessages.h"
#include "nsIAppsService.h"
#include "mozIApplication.h"
#ifdef MOZ_MEDIA_NAVIGATOR
#include "MediaManager.h"
@ -1849,6 +1851,38 @@ bool Navigator::HasInputMethodSupport(JSContext* /* unused */,
win && CheckPermission(win, "input"));
}
/* static */
bool
Navigator::HasDataStoreSupport(JSContext* /* unused */, JSObject* aGlobal)
{
// First of all, the general pref has to be turned on.
bool enabled = false;
Preferences::GetBool("dom.datastore.enabled", &enabled);
NS_ENSURE_TRUE(enabled, false);
// Just for testing, we can enable DataStore for any kind of app.
if (Preferences::GetBool("dom.testing.datastore_enabled_for_hosted_apps", false)) {
return true;
}
nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(aGlobal);
if (!win) {
return false;
}
nsIDocument* doc = win->GetExtantDoc();
if (!doc || !doc->NodePrincipal()) {
return false;
}
uint16_t status;
if (NS_FAILED(doc->NodePrincipal()->GetAppStatus(&status))) {
return false;
}
return status == nsIPrincipal::APP_STATUS_CERTIFIED;
}
/* static */
already_AddRefed<nsPIDOMWindow>
Navigator::GetWindowFromGlobal(JSObject* aGlobal)

View File

@ -286,6 +286,8 @@ public:
static bool HasInputMethodSupport(JSContext* /* unused */, JSObject* aGlobal);
static bool HasDataStoreSupport(JSContext* /* unused */, JSObject* aGlobal);
nsPIDOMWindow* GetParentObject() const
{
return GetWindow();

View File

@ -68,6 +68,13 @@ this.DataStoreChangeNotifier = {
receiveMessage: function(aMessage) {
debug("receiveMessage");
let prefName = 'dom.testing.datastore_enabled_for_hosted_apps';
if ((Services.prefs.getPrefType(prefName) == Services.prefs.PREF_INVALID ||
!Services.prefs.getBoolPref(prefName)) &&
!aMessage.target.assertAppHasStatus(Ci.nsIPrincipal.APP_STATUS_CERTIFIED)) {
return;
}
switch (aMessage.name) {
case "DataStore:Changed":
this.broadcastMessage("DataStore:Changed:Return:OK", aMessage.data);

View File

@ -234,6 +234,10 @@ DataStoreService.prototype = {
// window, so we can skip the ipc communication.
if (self.inParent) {
let stores = self.getDataStoresInfo(aName, aWindow.document.nodePrincipal.appId);
if (stores === null) {
reject(new aWindow.DOMError("SecurityError", "Access denied"));
return;
}
self.getDataStoreCreate(aWindow, resolve, stores);
} else {
// This method can be called in the child so we need to send a request
@ -252,6 +256,20 @@ DataStoreService.prototype = {
getDataStoresInfo: function(aName, aAppId) {
debug('GetDataStoresInfo');
let appsService = Cc["@mozilla.org/AppsService;1"]
.getService(Ci.nsIAppsService);
let app = appsService.getAppByLocalId(aAppId);
if (!app) {
return null;
}
let prefName = "dom.testing.datastore_enabled_for_hosted_apps";
if (app.appStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED &&
(Services.prefs.getPrefType(prefName) == Services.prefs.PREF_INVALID ||
!Services.prefs.getBoolPref(prefName))) {
return null;
}
let results = [];
if (aName in this.stores) {

View File

@ -13,6 +13,7 @@ function debug(s) {
}
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1",
@ -40,6 +41,13 @@ this.DataStoreServiceInternal = {
return;
}
let prefName = 'dom.testing.datastore_enabled_for_hosted_apps';
if ((Services.prefs.getPrefType(prefName) == Services.prefs.PREF_INVALID ||
!Services.prefs.getBoolPref(prefName)) &&
!aMessage.target.assertAppHasStatus(Ci.nsIPrincipal.APP_STATUS_CERTIFIED)) {
return;
}
let msg = aMessage.data;
if (!aMessage.principal ||
@ -49,6 +57,10 @@ this.DataStoreServiceInternal = {
}
msg.stores = dataStoreService.getDataStoresInfo(msg.name, aMessage.principal.appId);
if (msg.stores === null) {
aMessage.target.sendAsyncMessage("DataStore:Get:Return:KO");
return;
}
aMessage.target.sendAsyncMessage("DataStore:Get:Return:OK", msg);
}
}

View File

@ -0,0 +1,23 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test for DataStore just for certified Apps</title>
</head>
<body>
<div id="container"></div>
<script type="application/javascript;version=1.7">
function ok(a, msg) {
alert((a ? 'OK' : 'KO')+ ' ' + msg)
}
function finish() {
alert('DONE');
}
ok(!("getDataStores" in navigator), "DataStore not available");
finish();
</script>
</body>
</html>

View File

@ -11,6 +11,7 @@ support-files =
file_arrays.html
file_sync.html
file_bug924104.html
file_certifiedApp.html
[test_app_install.html]
[test_readonly.html]
@ -20,3 +21,4 @@ support-files =
[test_oop.html]
[test_sync.html]
[test_bug924104.html]
[test_certifiedApp.html]

View File

@ -27,7 +27,8 @@
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.datastore.enabled", true],
["dom.promise.enabled", true],
["dom.testing.ignore_ipc_principal", true]]}, function() {
["dom.testing.ignore_ipc_principal", true],
["dom.testing.datastore_enabled_for_hosted_apps", true]]}, function() {
gGenerator.next(); });
});
@ -44,11 +45,6 @@
function runTest() {
ok("getDataStores" in navigator, "getDataStores exists");
is(typeof navigator.getDataStores, "function", "getDataStores exists and it's a function");
navigator.getDataStores('foo').then(function(stores) {
is(stores.length, 0, "getDataStores('foo') returns 0 elements");
continueTest();
}, cbError);
yield undefined;
SpecialPowers.setAllAppsLaunchable(true);
SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);

View File

@ -78,7 +78,8 @@
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
["dom.datastore.enabled", true],
["dom.testing.ignore_ipc_principal", true]]}, runTest);
["dom.testing.ignore_ipc_principal", true],
["dom.testing.datastore_enabled_for_hosted_apps", true]]}, runTest);
},
function() {

View File

@ -78,7 +78,8 @@
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
["dom.datastore.enabled", true],
["dom.testing.ignore_ipc_principal", true]]}, runTest);
["dom.testing.ignore_ipc_principal", true],
["dom.testing.datastore_enabled_for_hosted_apps", true]]}, runTest);
},
function() {

View File

@ -78,7 +78,8 @@
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
["dom.datastore.enabled", true],
["dom.testing.ignore_ipc_principal", true]]}, runTest);
["dom.testing.ignore_ipc_principal", true],
["dom.testing.datastore_enabled_for_hosted_apps", true]]}, runTest);
},
function() {

View File

@ -0,0 +1,132 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test DataStore for certifiedApp only</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<div id="container"></div>
<script type="application/javascript;version=1.7">
var gHostedManifestURL = 'http://test/tests/dom/datastore/tests/file_app.sjs?testToken=file_certifiedApp.html';
var gApp;
function cbError() {
ok(false, "Error callback invoked");
finish();
}
function installApp() {
var request = navigator.mozApps.install(gHostedManifestURL);
request.onerror = cbError;
request.onsuccess = function() {
gApp = request.result;
runTest();
}
}
function uninstallApp() {
// Uninstall the app.
var request = navigator.mozApps.mgmt.uninstall(gApp);
request.onerror = cbError;
request.onsuccess = function() {
// All done.
info("All done");
runTest();
}
}
function testApp() {
var ifr = document.createElement('iframe');
ifr.setAttribute('mozbrowser', 'true');
ifr.setAttribute('mozapp', gApp.manifestURL);
ifr.setAttribute('src', gApp.manifest.launch_path);
var domParent = document.getElementById('container');
// Set us up to listen for messages from the app.
var listener = function(e) {
var message = e.detail.message;
if (/^OK/.exec(message)) {
ok(true, "Message from app: " + message);
} else if (/KO/.exec(message)) {
ok(false, "Message from app: " + message);
} else if (/DONE/.exec(message)) {
ok(true, "Messaging from app complete");
ifr.removeEventListener('mozbrowsershowmodalprompt', listener);
domParent.removeChild(ifr);
runTest();
}
}
// This event is triggered when the app calls "alert".
ifr.addEventListener('mozbrowsershowmodalprompt', listener, false);
domParent.appendChild(ifr);
}
var tests = [
function() {
ok(!("getDataStores" in navigator), "getDataStores should not exist");
runTest();
},
// Permissions
function() {
SpecialPowers.pushPermissions(
[{ "type": "browser", "allow": 1, "context": document },
{ "type": "embed-apps", "allow": 1, "context": document },
{ "type": "webapps-manage", "allow": 1, "context": document }], runTest);
},
// Preferences
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
["dom.datastore.enabled", true],
["dom.testing.ignore_ipc_principal", true]]}, runTest);
},
function() {
SpecialPowers.setAllAppsLaunchable(true);
SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
runTest();
},
// No confirmation needed when an app is installed
function() {
SpecialPowers.autoConfirmAppInstall(runTest);
},
// Installing the app
installApp,
// Run tests in app
testApp,
// Uninstall the app
uninstallApp
];
function runTest() {
if (!tests.length) {
finish();
return;
}
var test = tests.shift();
test();
}
function finish() {
SimpleTest.finish();
}
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SimpleTest.waitForExplicitFinish();
runTest();
</script>
</body>
</html>

View File

@ -122,7 +122,8 @@
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
["dom.datastore.enabled", true],
["dom.testing.ignore_ipc_principal", true]]}, runTest);
["dom.testing.ignore_ipc_principal", true],
["dom.testing.datastore_enabled_for_hosted_apps", true]]}, runTest);
},
// Enabling mozBrowser

View File

@ -78,11 +78,9 @@
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
["dom.datastore.enabled", true],
["dom.testing.ignore_ipc_principal", true]]}, runTest);
},
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.ipc.browser_frames.oop_by_default", true]]}, runTest);
["dom.ipc.browser_frames.oop_by_default", true],
["dom.testing.ignore_ipc_principal", true],
["dom.testing.datastore_enabled_for_hosted_apps", true]]}, runTest);
},
function() {

View File

@ -102,7 +102,8 @@
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
["dom.datastore.enabled", true],
["dom.testing.ignore_ipc_principal", true]]}, runTest);
["dom.testing.ignore_ipc_principal", true],
["dom.testing.datastore_enabled_for_hosted_apps", true]]}, runTest);
</script>
</body>
</html>

View File

@ -78,7 +78,8 @@
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.promise.enabled", true],
["dom.datastore.enabled", true],
["dom.testing.ignore_ipc_principal", true]]}, runTest);
["dom.testing.ignore_ipc_principal", true],
["dom.testing.datastore_enabled_for_hosted_apps", true]]}, runTest);
},
function() {

View File

@ -96,14 +96,22 @@ AssertAppStatus(PBrowserParent* aActor,
TabParent* tab = static_cast<TabParent*>(aActor);
nsCOMPtr<mozIApplication> app = tab->GetOwnOrContainingApp();
bool valid = false;
if (app) {
unsigned short appStatus = 0;
if (NS_SUCCEEDED(app->GetAppStatus(&appStatus))) {
return appStatus == aStatus;
valid = appStatus == aStatus;
}
}
return false;
if (!valid) {
printf_stderr("Security problem: Content process does not have `%d' status. It will be killed.\n", aStatus);
ContentParent* process = tab->Manager();
process->KillHard();
}
return valid;
}
bool

View File

@ -30,7 +30,7 @@ enum AssertAppProcessType {
};
/**
* Return true iff the specified browser has the specified capability.
* Return true if the specified browser has the specified capability.
* If this returns false, the browser didn't have the capability and
* will be killed.
*/
@ -39,12 +39,16 @@ AssertAppProcess(mozilla::dom::PBrowserParent* aActor,
AssertAppProcessType aType,
const char* aCapability);
/**
* Return true if the specified app has the specified status.
* If this returns false, the browser will be killed.
*/
bool
AssertAppStatus(mozilla::dom::PBrowserParent* aActor,
unsigned short aStatus);
/**
* Return true iff any of the PBrowsers loaded in this content process
* Return true if any of the PBrowsers loaded in this content process
* has the specified capability. If this returns false, the process
* didn't have the capability and will be killed.
*/
@ -53,6 +57,11 @@ AssertAppProcess(mozilla::dom::PContentParent* aActor,
AssertAppProcessType aType,
const char* aCapability);
/**
* Return true if any of the PBrowsers loaded in this content process
* has an app with the specified status. If this returns false, the process
* didn't have the status and will be killed.
*/
bool
AssertAppStatus(mozilla::dom::PContentParent* aActor,
unsigned short aStatus);

View File

@ -115,7 +115,7 @@ Navigator implements NavigatorBattery;
// https://wiki.mozilla.org/WebAPI/DataStore
[NoInterfaceObject]
interface NavigatorDataStore {
[Throws, NewObject, Pref="dom.datastore.enabled"]
[Throws, NewObject, Func="Navigator::HasDataStoreSupport"]
Promise getDataStores(DOMString name);
};
Navigator implements NavigatorDataStore;

View File

@ -274,6 +274,8 @@
"dom/browser-element/mochitest/test_browserElement_inproc_CloseFromOpener.html":"",
"dom/browser-element/":"",
"dom/datastore/tests/test_certifiedApp.html":"",
"dom/file/test/test_progress_events.html":"All of these fail fairly regularly with: UnknownError: The operation failed for reasons unrelated to the database itself and not covered by any other error code. at http://mochi.test:8888/tests/dom/file/test/helpers.js:126",
"dom/file/test/test_request_readyState.html":"",
"dom/file/test/test_stream_tracking.html":"",

View File

@ -282,6 +282,8 @@
"dom/browser-element/mochitest/test_browserElement_inproc_CloseFromOpener.html":"",
"dom/browser-element/":"",
"dom/datastore/tests/test_certifiedApp.html":"",
"dom/file/test/test_progress_events.html":"All of these fail fairly regularly with: UnknownError: The operation failed for reasons unrelated to the database itself and not covered by any other error code. at http://mochi.test:8888/tests/dom/file/test/helpers.js:126",
"dom/file/test/test_request_readyState.html":"",
"dom/file/test/test_stream_tracking.html":"",

View File

@ -7,3 +7,4 @@ user_pref("dom.ipc.tabs.disabled", false);
user_pref("dom.ipc.browser_frames.oop_by_default", false);
user_pref("dom.mozBrowserFramesWhitelist","app://test-container.gaiamobile.org,http://mochi.test:8888");
user_pref("marionette.force-local", true);
user_pref("dom.testing.datastore_enabled_for_hosted_apps", true);