Bug 880043 - Signed packaged installation tests. r=fabrice

This commit is contained in:
David Clarke 2014-01-21 18:53:55 +01:00
parent ce5716da48
commit 9eb9e7237a
19 changed files with 437 additions and 3 deletions

View File

@ -0,0 +1,36 @@
/* 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/. */
"use strict";
const Ci = Components.interfaces;
const Cu = Components.utils;
this.EXPORTED_SYMBOLS = [
"TrustedRootCertificate"
];
const APP_TRUSTED_ROOTS= ["AppMarketplaceProdPublicRoot",
"AppMarketplaceProdReviewersRoot",
"AppMarketplaceDevPublicRoot",
"AppMarketplaceDevReviewersRoot",
"AppXPCShellRoot"];
this.TrustedRootCertificate = {
_index: Ci.nsIX509CertDB.AppMarketplaceProdPublicRoot,
get index() {
return this._index;
},
set index(aIndex) {
// aIndex should be one of the
// Ci.nsIX509CertDB AppTrustedRoot defined values
let found = APP_TRUSTED_ROOTS.some((trustRoot) => {
return Ci.nsIX509CertDB[trustRoot] === aIndex;
});
if (found) {
this._index = aIndex;
}
}
};

View File

@ -43,6 +43,9 @@ Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TrustedRootCertificate",
"resource://gre/modules/StoreTrustAnchor.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PermissionsInstaller",
"resource://gre/modules/PermissionsInstaller.jsm");
@ -3054,7 +3057,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
let deferred = Promise.defer();
aCertDb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppMarketplaceProdPublicRoot, aZipFile,
TrustedRootCertificate.index, aZipFile,
function(aRv, aZipReader) {
deferred.resolve([aRv, aZipReader]);
}

View File

@ -30,6 +30,7 @@ EXTRA_JS_MODULES += [
'OfflineCacheInstaller.jsm',
'PermissionsInstaller.jsm',
'PermissionsTable.jsm',
'StoreTrustAnchor.jsm',
]
EXTRA_PP_JS_MODULES += [

View File

@ -0,0 +1,10 @@
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cu = Components.utils;
Cu.import("resource://gre/modules/StoreTrustAnchor.jsm");
// This is all that's needed now
TrustedRootCertificate.index = Ci.nsIX509CertDB.AppXPCShellRoot;
sendAsyncMessage("addCertCompleted");

View File

@ -1,6 +1,7 @@
[DEFAULT]
skip-if = toolkit=='gonk' || e10s #b2g(bug 972927, nearly perma-fail) b2g-debug(bug 972927, nearly perma-fail)
support-files =
chromeAddCert.js
file_app.sjs
file_app.template.html
file_cached_app.template.appcache
@ -9,16 +10,20 @@ support-files =
file_packaged_app.sjs
file_packaged_app.template.html
file_packaged_app.template.webapp
signed_app.sjs
signed_app_template.webapp
signed/*
[test_app_update.html]
skip-if = (toolkit == 'gonk' && debug) #debug-only failure
[test_bug_795164.html]
[test_install_receipts.html]
[test_packaged_app_common.js]
[test_packaged_app_install.html]
skip-if = (toolkit == 'gonk' && debug) #debug-only failure
[test_packaged_app_update.html]
skip-if = toolkit=='gonk' # b2g(bug 915879) b2g-debug(debug-only timeout)
[test_receipt_operations.html]
[test_signed_pkg_install.html]
[test_uninstall_errors.html]
skip-if = (toolkit == 'gonk' && debug) #debug-only timeout
[test_receipt_operations.html]
[test_install_receipts.html]

View File

@ -10,3 +10,4 @@ MOCHITEST_MANIFESTS += ['mochitest.ini']
MOCHITEST_CHROME_MANIFESTS += ['chrome.ini']
DIRS += ['signed']

Binary file not shown.

View File

@ -0,0 +1,5 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,111 @@
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
// From prio.h
const PR_RDWR = 0x04;
const PR_CREATE_FILE = 0x08;
const PR_TRUNCATE = 0x20;
const CUR_WORK_DIR = "CurWorkD";
Cu.import("resource://gre/modules/NetUtil.jsm");
var gBasePath = "tests/dom/apps/tests/";
var gMiniManifestTemplate = "signed_app_template.webapp";
var gAppName = "Simple App";
var gDevName = "David Clarke";
var gDevUrl = "http://dev.url";
function handleRequest(request, response) {
var query = getQuery(request);
response.setHeader("Access-Control-Allow-Origin", "*", false);
var version = ("version" in query) ? query.version : "1";
var app = ("app" in query) ? query.app : "valid";
var prevVersion = getState("version");
if (version != prevVersion) {
setState("version", version);
}
var packageName = app + "_app_" + version + ".zip";
setState("packageName", packageName);
var packagePath = "/" + gBasePath + "signed/" + packageName;
setState("packagePath", packagePath);
var packageSize = readSize(packagePath);
var etag = getEtag(request, version);
if (etagMatches(request, etag)) {
dump("Etags Match. Sending 304\n");
response.setStatusLine(request.httpVersion, "304", "Not modified");
return;
}
response.setHeader("Etag", etag, false);
// Serve the mini-manifest corresponding to the requested app version.
var template = gBasePath + gMiniManifestTemplate;
response.setHeader("Content-Type",
"application/x-web-app-manifest+json", false);
var manifest = makeResource(template, version, packagePath, packageSize,
gAppName, gDevName, gDevUrl);
response.write(manifest);
}
function getQuery(request) {
var query = {};
request.queryString.split('&').forEach(function (val) {
var [name, value] = val.split('=');
query[decodeURIComponent(name)] = decodeURIComponent(value);
});
return query;
}
function getEtag(request, version) {
return request.queryString.replace(/[&=]/g, '-') + '-' + version;
}
function etagMatches(request, etag) {
return request.hasHeader("If-None-Match") &&
request.getHeader("If-None-Match") == etag;
}
// File and resources helpers
function readSize(path) {
var file = Cc["@mozilla.org/file/directory_service;1"].
getService(Ci.nsIProperties).get(CUR_WORK_DIR, Ci.nsILocalFile);
var split = path.split("/");
for (var i = 0; i < split.length; ++i) {
file.append(split[i]);
}
return file.fileSize;
}
function readFile(path) {
var file = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties)
.get(CUR_WORK_DIR, Ci.nsILocalFile);
var fstream = Cc["@mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
var split = path.split("/");
for (var i = 0; i < split.length; ++i) {
file.append(split[i]);
}
fstream.init(file, -1, 0, 0);
var data = NetUtil.readInputStreamToString(fstream, fstream.available());
fstream.close();
return data;
}
function makeResource(templatePath, version, packagePath, packageSize,
appName, developerName, developerUrl) {
var res = readFile(templatePath, false).
replace(/VERSIONTOKEN/g, version).
replace(/PACKAGEPATHTOKEN/g, packagePath).
replace(/PACKAGESIZETOKEN/g, packageSize).
replace(/NAMETOKEN/g, appName);
return res;
}

View File

@ -0,0 +1,9 @@
{
"name" : "NAMETOKEN",
"version" : "VERSIONTOKEN",
"size" : PACKAGESIZETOKEN,
"package_path": "PACKAGEPATHTOKEN",
"description": "Updated even faster than Firefox, just to annoy slashdotters",
"launch_path": "tests/dom/apps/tests/signed_app.sjs",
"default_locale": "en-US"
}

View File

@ -13,18 +13,31 @@ var PackagedTestHelper = (function PackagedTestHelper() {
var gAppName = "appname";
var gApp = null;
var gInstallOrigin = "http://mochi.test:8888";
var timeoutID;
function timeoutError() {
ok(false, "Timeout! Probably waiting on a app installation event");
info("Finishing this test suite!");
finish();
}
function debug(aMsg) {
//dump("== PackageTestHelper debug == " + aMsg + "\n");
}
function next() {
if (timeoutID) {
clearTimeout(timeoutID);
}
index += 1;
if (index >= steps.length) {
ok(false, "Shouldn't get here!");
return;
}
try {
// There's nothing here that should take more than 30 seconds, even on
// heavy loads. So there's no need to stop further tests for five minutes.
timeoutID = setTimeout(timeoutError, 30000);
steps[index]();
} catch(ex) {
ok(false, "Caught exception", ex);
@ -36,6 +49,9 @@ var PackagedTestHelper = (function PackagedTestHelper() {
}
function finish() {
if (timeoutID) {
clearTimeout(timeoutID);
}
SpecialPowers.setAllAppsLaunchable(launchableValue);
SpecialPowers.removePermission("webapps-manage", document);
SimpleTest.finish();
@ -180,6 +196,9 @@ var PackagedTestHelper = (function PackagedTestHelper() {
is(aApp.readyToApplyDownload, aExpectedApp.readyToApplyDownload,
"Check readyToApplyDownload");
}
if (typeof aExpectedApp.origin !== "undefined") {
is(aApp.origin, aExpectedApp.origin, "Check origin");
}
if (aLaunchable) {
if (aUninstall) {
checkUninstallApp(aApp);
@ -218,6 +237,7 @@ var PackagedTestHelper = (function PackagedTestHelper() {
checkAppState: checkAppState,
checkAppDownloadError: checkAppDownloadError,
get gSJSPath() { return gSJSPath; },
set gSJSPath(aValue) { gSJSPath = aValue },
get gSJS() { return gSJS; },
get gAppName() { return gAppName;},
get gApp() { return gApp; },

View File

@ -0,0 +1,233 @@
<!DOCTYPE html>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=880043
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 880043 Packaged apps installation and update</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="test_packaged_app_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=880043">Mozilla Bug 880043</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
"use strict";
const Ci = SpecialPowers.Ci;
const Cc = SpecialPowers.Cc;
const Cu = SpecialPowers.Cu;
var launchableValue;
var index = -1;
var gDebug = false;
var gApp = null;
var gAppName = "Simple App";
var gInstallOrigin = "http://mochi.test:8888/";
var gSJSPath = "tests/dom/apps/tests/signed_app.sjs";
var gSJS = gInstallOrigin + gSJSPath;
var gPackagePath = gInstallOrigin + "tests/dom/apps/tests/";
var gSignedAppOriginsStr ="";
SimpleTest.waitForExplicitFinish();
function checkAppOnInstallError(aMiniManifestURL, aExpectedError) {
navigator.mozApps.mgmt.oninstall = function(evt) {
info("Got oninstall event");
gApp = evt.application;
gApp.ondownloaderror = function() {
is(gApp.downloadError.name, aExpectedError,
"Download fails with expected error: " + aExpectedError);
if (gApp.downloadError.name != aExpectedError) {
PackagedTestHelper.finish();
} else {
PackagedTestHelper.next();
}
};
gApp.ondownloadsuccess = function() {
ok(false, "App download should fail");
PackagedTestHelper.finish();
};
};
var request = navigator.mozApps.installPackage(aMiniManifestURL);
request.onerror = function(evt) {
ok(false, "Application should throw the error inside oninstall: " + evt.error.name);
PackagedTestHelper.finish();
};
request.onsuccess = function() {
info("Application install returns successfully");
};
}
function checkUninstallApp(aApp) {
var req = navigator.mozApps.mgmt.uninstall(aApp);
req.onsuccess = function() {
info("App uninstalled");
aApp.ondownloadsuccess = null;
aApp.ondownloaderror = null;
aApp.onprogress = null;
PackagedTestHelper.next();
};
req.onerror = function(evt) {
ok(false, "App uninstallation should succeed (got unexpected " +
evt.target.error.name + ")");
PackagedTestHelper.finish();
};
}
var steps = [
function() {
// Set up
info("Test Initial Setup");
gSignedAppOriginsStr = SpecialPowers.getCharPref("dom.mozApps.signed_apps_installable_from");
var signedAppOriginsStr = gSignedAppOriginsStr.concat("," + gInstallOrigin.slice(0, -1));
SpecialPowers.pushPrefEnv({'set': [['dom.mozApps.signed_apps_installable_from', signedAppOriginsStr]]}, function() {
var url = SimpleTest.getTestFileURL("chromeAddCert.js");
var script = SpecialPowers.loadChromeScript(url);
script.addMessageListener("addCertCompleted", function() {
launchableValue = SpecialPowers.setAllAppsLaunchable(true);
SpecialPowers.addPermission("webapps-manage", true, document);
info("Test CA Certificate Selected");
PackagedTestHelper.next();
script.destroy();
});
});
},
function() {
info("autoConfirmAppInstall");
SpecialPowers.autoConfirmAppInstall(PackagedTestHelper.next);
},
function() {
info("== TEST == Install packaged app");
var miniManifestURL = gSJS + "?" + "app=valid&" + "version=1";
navigator.mozApps.mgmt.oninstall = function(evt) {
info("Got oninstall event");
gApp = evt.application;
gApp.ondownloaderror = function() {
ok(false, "Download should succeed (got error: " +
gApp.downloadError.name + ")");
PackagedTestHelper.finish();
};
gApp.ondownloadsuccess = function() {
info("App downloaded");
var expected = {
name: gAppName,
manifestURL: miniManifestURL,
installOrigin: gInstallOrigin.slice(0, -1),
progress: 0,
installState: "installed",
downloadAvailable: false,
downloading: false,
readyToApplyDownload: false,
};
PackagedTestHelper.checkAppState(gApp, 1, expected,
true, false, PackagedTestHelper.next);
};
};
info("Installing app: " + miniManifestURL);
var request = navigator.mozApps.installPackage(miniManifestURL);
request.onerror = function(evt) {
ok(false, "Application should have been correctly installed (error: " +
JSON.stringify(evt));
};
request.onsuccess = function() {
info("Application installed");
};
},
function() {
info("== TEST == Uninstall a signed app");
// Uninstall App
checkUninstallApp(gApp);
},
function() {
info("== TEST == Install a corrupted package");
//Scenario: Corrupted package
var miniManifestURL = gSJS + "?" + "app=corrupt&" + "version=1";
checkAppOnInstallError(miniManifestURL, "APP_PACKAGE_CORRUPTED");
},
function() {
info("== TEST == Install a unsigned app from a trusted store");
//Scenario: Unsigned App from an origin that requires signed apps
var miniManifestURL = gSJS + "?" + "app=unknown_issuer&" + "version=1";
checkAppOnInstallError(miniManifestURL, "INVALID_SIGNATURE");
},
function() {
info("== TEST == Install packaged app with origin");
var miniManifestURL = gSJS + "?" +
"app=origin&" +
"version=1";
navigator.mozApps.mgmt.oninstall = function(evt) {
info("Got oninstall event");
gApp = evt.application;
gApp.ondownloaderror = function() {
ok(false, "Download should succeed (got error: " +
gApp.downloadError.name + ")");
PackagedTestHelper.finish();
};
gApp.ondownloadsuccess = function() {
info("App downloaded");
var expected = {
name: gAppName,
manifestURL: miniManifestURL,
installOrigin: gInstallOrigin.slice(0, -1),
progress: 0,
installState: "installed",
downloadAvailable: false,
downloading: false,
origin: "app://test.origin.privileged.app",
readyToApplyDownload: false,
};
PackagedTestHelper.checkAppState(gApp, 1, expected,
true, false, PackagedTestHelper.next);
};
};
info("Installing app: " + miniManifestURL);
var request = navigator.mozApps.installPackage(miniManifestURL);
request.onerror = function(evt) {
ok(false, "Application should have been correctly installed (error: " +
JSON.stringify(evt));
};
request.onsuccess = function() {
info("Application installed");
};
},
function() {
info("== TEST == Install app from an invalid source");
// Scenario: This is where an unexpected store is signing packages and
// attempting to install. Please note that after this test you cannot
// add new successful tests without changing the preference again
SpecialPowers.pushPrefEnv(
{'set': [['dom.mozApps.signed_apps_installable_from',
gSignedAppOriginsStr]]},
function() {
var miniManifestURL = gSJS + "?" + "app=valid&" + "version=1";
checkAppOnInstallError(miniManifestURL, "INSTALL_FROM_DENIED");
});
},
function() {
info("all done!");
PackagedTestHelper.finish();
}
];
PackagedTestHelper.setSteps(steps);
PackagedTestHelper.gSJSPath = gSJSPath;
addLoadEvent(PackagedTestHelper.start);
</script>
</pre>
</body>
</html>