Bug 786296 - Remove permissions when an app is uninstalled. r=jlebar,fabrice

This commit is contained in:
Mounir Lamouri 2012-08-31 11:34:28 -03:00
parent 5dac27b993
commit 41418e78b5
14 changed files with 329 additions and 12 deletions

View File

@ -47,6 +47,11 @@ AppsService.prototype = {
return DOMApplicationRegistry.getManifestURLByLocalId(aLocalId);
},
getAppFromObserverMessage: function getAppFromObserverMessage(aMessage) {
debug("getAppFromObserverMessage( " + aMessage + " )");
return DOMApplicationRegistry.getAppFromObserverMessage(aMessage);
},
classID : APPS_SERVICE_CID,
QueryInterface : XPCOMUtils.generateQI([Ci.nsIAppsService])
}

View File

@ -76,6 +76,11 @@ let DOMApplicationRegistry = {
getManifestURLByLocalId: function getManifestURLByLocalId(aLocalId) {
debug("getManifestURLByLocalId " + aLocalId);
return AppsUtils.getManifestURLByLocalId(this.webapps, aLocalId);
},
getAppFromObserverMessage: function getAppFromObserverMessage(aMessage) {
debug("getAppFromObserverMessage " + aMessage);
return AppsUtils.getAppFromObserverMessage(this.webapps. aMessage);
}
}

View File

@ -105,5 +105,20 @@ let AppsUtils = {
}
return "";
},
getAppFromObserverMessage: function(aApps, aMessage) {
let data = JSON.parse(aMessage);
for (let id in aApps) {
let app = aApps[id];
if (app.origin != data.origin) {
continue;
}
return this.cloneAsMozIApplication(app);
}
return null;
}
}

View File

@ -780,6 +780,10 @@ let DOMApplicationRegistry = {
return AppsUtils.getAppLocalIdByManifestURL(this.webapps, aManifestURL);
},
getAppFromObserverMessage: function(aMessage) {
return AppsUtils.getAppFromObserverMessage(this.webapps, aMessage);
},
getAllWithoutManifests: function(aCallback) {
let result = {};
for (let id in this.webapps) {

View File

@ -11,7 +11,7 @@
* We expose Gecko-internal helpers related to "web apps" through this
* sub-interface.
*/
[scriptable, uuid(acf46a46-729a-4ab4-9da3-8d59ecfd103d)]
[scriptable, uuid(764e8930-ff06-4f23-9a6a-8523b93ac09f)]
interface mozIApplication: mozIDOMApplication
{
/* Return true if this app has |permission|. */
@ -19,4 +19,7 @@ interface mozIApplication: mozIDOMApplication
/* Application status as defined in nsIPrincipal. */
readonly attribute unsigned short appStatus;
/* Returns the local id of the app (not the uuid used for sync). */
readonly attribute unsigned long localId;
};

View File

@ -5,6 +5,7 @@
#include "domstubs.idl"
interface mozIDOMApplication;
interface mozIApplication;
%{C++
#define APPS_SERVICE_CID { 0x05072afa, 0x92fe, 0x45bf, { 0xae, 0x22, 0x39, 0xb6, 0x9c, 0x11, 0x70, 0x58 } }
@ -15,7 +16,7 @@ interface mozIDOMApplication;
* This service allows accessing some DOMApplicationRegistry methods from
* non-javascript code.
*/
[scriptable, uuid(04e4ef3c-1a30-45bc-ab08-291820f13872)]
[scriptable, uuid(1f0ec00c-57c7-4ad2-a648-1359aa390360)]
interface nsIAppsService : nsISupports
{
mozIDOMApplication getAppByManifestURL(in DOMString manifestURL);
@ -37,4 +38,11 @@ interface nsIAppsService : nsISupports
* Returns the manifest URL associated to this localId.
*/
DOMString getManifestURLByLocalId(in unsigned long localId);
/**
* Returns the app that is related to the message.
* This is a helper to not have to worry about what is the actual structure
* of the message when listening to one.
*/
mozIApplication getAppFromObserverMessage(in DOMString message);
};

View File

@ -24,7 +24,7 @@
<script>
steps = [get_installed_returns_nothing, install_super_crazy, get_self_returns_nothing,
steps = [get_installed_returns_nothing, get_self_returns_nothing,
install_wild_crazy, uninstall_wild_crazy, tearDown];
runAll(steps);
@ -38,14 +38,6 @@ function get_installed_returns_nothing(next) {
next);
}
function install_super_crazy(next) {
debug("in " + arguments.callee.name);
var appURL = SERVERS['super_crazy'];
install(appURL, ok, function() {
getInstalled([appURL], ok, next);
});
}
function get_self_returns_nothing(next) {
debug("in " + arguments.callee.name);
mozAppscb(navigator.mozApps.getSelf(),

View File

@ -24,6 +24,8 @@
#include "nsIPrincipal.h"
#include "nsContentUtils.h"
#include "nsIScriptSecurityManager.h"
#include "nsIAppsService.h"
#include "mozIApplication.h"
static nsPermissionManager *gPermissionManager = nullptr;
@ -121,6 +123,33 @@ GetHostForPrincipal(nsIPrincipal* aPrincipal, nsACString& aHost)
return NS_OK;
}
class AppUninstallObserver : public nsIObserver {
public:
NS_DECL_ISUPPORTS
// nsIObserver implementation.
NS_IMETHODIMP
Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *data)
{
MOZ_ASSERT(!nsCRT::strcmp(aTopic, "webapps-uninstall"));
nsCOMPtr<nsIAppsService> appsService = do_GetService("@mozilla.org/AppsService;1");
nsCOMPtr<mozIApplication> app;
appsService->GetAppFromObserverMessage(nsAutoString(data), getter_AddRefs(app));
NS_ENSURE_TRUE(app, NS_ERROR_UNEXPECTED);
uint32_t appId;
app->GetLocalId(&appId);
MOZ_ASSERT(appId != nsIScriptSecurityManager::NO_APP_ID);
nsCOMPtr<nsIPermissionManager> permManager = do_GetService("@mozilla.org/permissionmanager;1");
return permManager->RemovePermissionsForApp(appId);
}
};
NS_IMPL_ISUPPORTS1(AppUninstallObserver, nsIObserver)
} // anonymous namespace
////////////////////////////////////////////////////////////////////////////////
@ -238,6 +267,13 @@ NS_IMETHODIMP DeleteFromMozHostListener::HandleCompletion(uint16_t aReason)
return NS_OK;
}
/* static */ void
nsPermissionManager::AppUninstallObserverInit()
{
nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1");
observerService->AddObserver(new AppUninstallObserver(), "webapps-uninstall", /* holdsWeak= */ false);
}
////////////////////////////////////////////////////////////////////////////////
// nsPermissionManager Implementation
@ -1071,6 +1107,85 @@ NS_IMETHODIMP nsPermissionManager::Observe(nsISupports *aSubject, const char *aT
return NS_OK;
}
PLDHashOperator
nsPermissionManager::GetPermissionsForApp(nsPermissionManager::PermissionHashKey* entry, void* arg)
{
GetPermissionsForAppStruct* data = static_cast<GetPermissionsForAppStruct*>(arg);
for (uint32_t i = 0; i < entry->GetPermissions().Length(); ++i) {
nsPermissionManager::PermissionEntry& permEntry = entry->GetPermissions()[i];
if (entry->GetKey()->mAppId != data->appId) {
continue;
}
data->permissions.AppendObject(new nsPermission(entry->GetKey()->mHost,
entry->GetKey()->mAppId,
entry->GetKey()->mIsInBrowserElement,
gPermissionManager->mTypeArray.ElementAt(permEntry.mType),
permEntry.mPermission,
permEntry.mExpireType,
permEntry.mExpireTime));
}
return PL_DHASH_NEXT;
}
NS_IMETHODIMP
nsPermissionManager::RemovePermissionsForApp(uint32_t aAppId)
{
ENSURE_NOT_CHILD_PROCESS;
NS_ENSURE_ARG(aAppId != nsIScriptSecurityManager::NO_APP_ID);
// We begin by removing all the permissions from the DB.
// This is not using a mozIStorageStatement because removing an app should be
// rare enough to not have to worry too much about performance.
// After clearing the DB, we call AddInternal() to make sure that all
// processes are aware of this change and the representation of the DB in
// memory is updated.
// We have to get all permissions associated with an application and then
// remove those because doing so in EnumerateEntries() would fail because
// we might happen to actually delete entries from the list.
nsCAutoString sql;
sql.AppendLiteral("DELETE FROM moz_hosts WHERE appId=");
sql.AppendInt(aAppId);
nsresult rv = mDBConn->ExecuteSimpleSQL(sql);
NS_ENSURE_SUCCESS(rv, rv);
GetPermissionsForAppStruct data(aAppId);
mPermissionTable.EnumerateEntries(GetPermissionsForApp, &data);
for (int32_t i=0; i<data.permissions.Count(); ++i) {
nsCAutoString host;
bool isInBrowserElement;
nsCAutoString type;
data.permissions[i]->GetHost(host);
data.permissions[i]->GetIsInBrowserElement(&isInBrowserElement);
data.permissions[i]->GetType(type);
nsCOMPtr<nsIPrincipal> principal;
if (NS_FAILED(GetPrincipal(host, aAppId, isInBrowserElement,
getter_AddRefs(principal)))) {
NS_ERROR("GetPrincipal() failed!");
continue;
}
AddInternal(principal,
type,
nsIPermissionManager::UNKNOWN_ACTION,
0,
nsIPermissionManager::EXPIRE_NEVER,
0,
nsPermissionManager::eNotify,
nsPermissionManager::eNoDBOperation);
}
return NS_OK;
}
//*****************************************************************************
//*** nsPermissionManager private methods
//*****************************************************************************

View File

@ -18,6 +18,7 @@
#include "nsPermission.h"
#include "nsHashKeys.h"
#include "nsAutoPtr.h"
#include "nsCOMArray.h"
class nsIPermission;
class nsIIDNService;
@ -190,6 +191,14 @@ public:
NotifyOperationType aNotifyOperation,
DBOperationType aDBOperation);
/**
* Initialize the "webapp-uninstall" observing.
* Will create a nsPermissionManager instance if needed.
* That way, we can prevent have nsPermissionManager created at startup just
* to be able to clear data when an application is uninstalled.
*/
static void AppUninstallObserverInit();
private:
int32_t GetTypeIndex(const char *aTypeString,
bool aAdd);
@ -237,6 +246,28 @@ private:
uint32_t aAppId,
bool aIsInBrowserElement);
/**
* This struct has to be passed as an argument to GetPermissionsForApp.
* |appId| has to be defined.
* |permissions| will be filed with permissions that are related to the app.
*/
struct GetPermissionsForAppStruct {
uint32_t appId;
nsCOMArray<nsIPermission> permissions;
GetPermissionsForAppStruct() MOZ_DELETE;
GetPermissionsForAppStruct(uint32_t aAppId)
: appId(aAppId)
{}
};
/**
* This method will return the list of all permissions that are related to a
* specific app.
* @param arg has to be an instance of GetPermissionsForAppStruct.
*/
static PLDHashOperator GetPermissionsForApp(nsPermissionManager::PermissionHashKey* entry, void* arg);
nsCOMPtr<nsIObserverService> mObserverService;
nsCOMPtr<nsIIDNService> mIDNService;

View File

@ -54,6 +54,7 @@ MOCHITEST_FILES = \
MOCHITEST_CHROME_FILES = \
test_permissionmanager_app_isolation.html \
test_app_uninstall.html \
$(NULL)
MOCHITEST_BROWSER_FILES = \

View File

@ -0,0 +1,129 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=786296
-->
<head>
<meta charset="utf-8">
<title>Tests that uninstalling app removes the permissions</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=786296">Mozilla Bug 786296</a>
<p id="display"></p>
<div id="content">
</div>
<pre id="test">
<script type="application/javascript;version=1.7">
/** Test for Bug 786296 **/
var Ci = Components.interfaces;
var Cc = Components.classes;
SimpleTest.waitForExplicitFinish();
var permManager = Cc["@mozilla.org/permissionmanager;1"]
.getService(Ci.nsIPermissionManager);
var appsService = Cc['@mozilla.org/AppsService;1']
.getService(Ci.nsIAppsService);
var secMan = Cc['@mozilla.org/scriptsecuritymanager;1']
.getService(Ci.nsIScriptSecurityManager);
var ioService = Cc["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
function confirmNextInstall() {
var panel = window.top.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.chromeEventHandler.ownerDocument.defaultView
.PopupNotifications.panel
panel.addEventListener("popupshown", function() {
panel.removeEventListener("popupshown", arguments.callee, false);
this.childNodes[0].button.doCommand();
}, false);
}
// If aAppId = -1, returns permissions count, regardless of app.
function getPermissionCountForApp(aAppId) {
var nbPermissions = 0;
var enumerator = permManager.enumerator;
while (enumerator.hasMoreElements()) {
var permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
if (permission.appId == aAppId || aAppId == -1) {
nbPermissions++;
}
}
return nbPermissions;
}
var previousDryRunValue = null;
try {
previousDryRunValue = SpecialPowers.getBoolPref('browser.mozApps.installer.dry_run');
} catch(e) {
}
SpecialPowers.setBoolPref('browser.mozApps.installer.dry_run', true);
permManager.addFromPrincipal(window.document.nodePrincipal, "webapps-manage",
Ci.nsIPermissionManager.ALLOW_ACTION);
var gManifestURL = "http://www.example.com:80/chrome/dom/tests/mochitest/webapps/apps/super_crazy.webapp";
confirmNextInstall();
navigator.mozApps.install(gManifestURL, null).onsuccess = function() {
var testAppId = appsService.getAppLocalIdByManifestURL(gManifestURL);
is(getPermissionCountForApp(testAppId), 0, "App should have no permission");
var currentPermissionCount = getPermissionCountForApp(-1);
var principal = secMan.getAppCodebasePrincipal(ioService.newURI("http://www.example.com", null, null),
testAppId, false);
permManager.addFromPrincipal(principal, "foobar", Ci.nsIPermissionManager.ALLOW_ACTION);
permManager.addFromPrincipal(principal, "foo", Ci.nsIPermissionManager.DENY_ACTION);
permManager.addFromPrincipal(principal, "bar", Ci.nsIPermissionManager.ALLOW_ACTION, Ci.nsIPermissionManager.EXPIRE_SESSION, 0);
principal = secMan.getAppCodebasePrincipal(ioService.newURI("http://www.example.com", null, null),
testAppId, true);
permManager.addFromPrincipal(principal, "foobar", Ci.nsIPermissionManager.ALLOW_ACTION);
principal = secMan.getAppCodebasePrincipal(ioService.newURI("http://www.example.org", null, null),
testAppId, false);
permManager.addFromPrincipal(principal, "foobar", Ci.nsIPermissionManager.ALLOW_ACTION);
is(getPermissionCountForApp(testAppId), 5, "App should have 5 permissions");
// Not installed means not installed as native app.
navigator.mozApps.mgmt.getNotInstalled().onsuccess = function() {
for (i in this.result) {
var app = this.result[i];
if (app.manifestURL == gManifestURL) {
app.uninstall().onsuccess = function() {
is(getPermissionCountForApp(testAppId), 0, "App should have no permissions");
is(getPermissionCountForApp(-1), currentPermissionCount,
"Number of permissions should not have changed");
SpecialPowers.setBoolPref('browser.mozApps.installer.dry_run', previousDryRunValue);
permManager.removeFromPrincipal(window.document.nodePrincipal, "webapps-manage",
Ci.nsIPermissionManager.ALLOW_ACTION);
SimpleTest.finish();
return;
};
}
}
};
};
</script>
</pre>
</body>
</html>

View File

@ -266,6 +266,7 @@ LOCAL_INCLUDES += -I$(srcdir)/../base \
-I$(topsrcdir)/caps/include \
-I$(topsrcdir)/netwerk/base/src \
-I$(topsrcdir)/content/svg/content/src \
-I$(topsrcdir)/extensions/cookie \
$(NULL)
ifdef MOZ_B2G_RIL #{

View File

@ -98,6 +98,7 @@
#include "nsWindowMemoryReporter.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/ipc/ProcessPriorityManager.h"
#include "nsPermissionManager.h"
extern void NS_ShutdownChainItemPool();
@ -252,6 +253,8 @@ nsLayoutStatics::Initialize()
InitProcessPriorityManager();
nsPermissionManager::AppUninstallObserverInit();
return NS_OK;
}

View File

@ -35,7 +35,7 @@ interface nsIURI;
interface nsIObserver;
interface nsIPrincipal;
[scriptable, uuid(cc423aaf-f088-4ec2-86ef-7733225773f9)]
[scriptable, uuid(da33450a-f3cb-4fdb-93ee-219644e450c2)]
interface nsIPermissionManager : nsISupports
{
/**
@ -169,6 +169,11 @@ interface nsIPermissionManager : nsISupports
* nsIPermission objects
*/
readonly attribute nsISimpleEnumerator enumerator;
/**
* Remove all permissions associated with a given app id.
*/
void removePermissionsForApp(in unsigned long appId);
};
%{ C++