Bug 1247685 - Send subscription keys to the Push server. r=mt

MozReview-Commit-ID: 2i3UqgNGlEt

--HG--
extra : rebase_source : 9c30f34c7de7c9fa7f04db5f224bad3ac0e68904
This commit is contained in:
Kit Cambridge 2016-03-22 16:29:16 -07:00
parent 21365c8060
commit 4f645f9bfe
7 changed files with 345 additions and 18 deletions

View File

@ -330,6 +330,7 @@ SubscriptionListener.prototype = {
scope: this._subInfo.record.scope,
originAttributes: this._subInfo.record.originAttributes,
systemRecord: this._subInfo.record.systemRecord,
appServerKey: this._subInfo.record.appServerKey,
ctime: Date.now(),
});

View File

@ -906,6 +906,7 @@ this.PushServiceWebSocket = {
originAttributes: tmp.record.originAttributes,
version: null,
systemRecord: tmp.record.systemRecord,
appServerKey: tmp.record.appServerKey,
ctime: Date.now(),
});
Services.telemetry.getHistogramById("PUSH_API_SUBSCRIBE_WS_TIME").add(Date.now() - tmp.ctime);
@ -1047,6 +1048,13 @@ this.PushServiceWebSocket = {
let data = {channelID: this._generateID(),
messageType: "register"};
if (record.appServerKey) {
data.key = ChromeUtils.base64URLEncode(record.appServerKey, {
// The Push server requires padding.
pad: true,
});
}
return new Promise((resolve, reject) => {
this._registerRequests.set(data.channelID, {
record: record,

View File

@ -12,6 +12,7 @@ support-files =
[test_has_permissions.html]
[test_permissions.html]
[test_register.html]
[test_register_key.html]
[test_multiple_register.html]
[test_multiple_register_during_service_activation.html]
[test_unregister.html]

View File

@ -0,0 +1,206 @@
<!DOCTYPE HTML>
<html>
<!--
Bug 1247685: Implement `applicationServerKey` for subscription association.
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/licenses/publicdomain/
-->
<head>
<title>Test for Bug 1247685</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
<script type="text/javascript" src="/tests/dom/push/test/test_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
</head>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1247685">Mozilla Bug 1247685</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
<script class="testbody" type="text/javascript">
var isTestingMismatchedKey = false;
var subscriptions = 0;
var testKey; // Generated in `start`.
function generateKey() {
return crypto.subtle.generateKey({
name: "ECDSA",
namedCurve: "P-256",
}, true, ["sign", "verify"]).then(cryptoKey =>
crypto.subtle.exportKey("raw", cryptoKey.publicKey)
).then(publicKey => new Uint8Array(publicKey));
}
var registration;
add_task(function* start() {
yield setupPrefsAndReplaceService({
register(pageRecord) {
ok(pageRecord.appServerKey.length > 0,
"App server key should not be empty");
if (pageRecord.appServerKey.length != 65) {
throw { result:
SpecialPowers.Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR };
}
if (isTestingMismatchedKey) {
throw { result:
SpecialPowers.Cr.NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR };
}
return {
endpoint: "https://example.com/push/" + (++subscriptions),
appServerKey: pageRecord.appServerKey,
};
},
registration(pageRecord) {
return {
endpoint: "https://example.com/push/subWithKey",
appServerKey: testKey,
};
},
});
yield setPushPermission(true);
testKey = yield generateKey();
var url = "worker.js" + "?" + (Math.random());
registration = yield navigator.serviceWorker.register(url, {scope: "."});
});
var controlledFrame;
add_task(function* createControlledIFrame() {
controlledFrame = yield injectControlledFrame();
});
add_task(function* emptyKey() {
try {
yield registration.pushManager.subscribe({
applicationServerKey: new ArrayBuffer(0),
});
ok(false, "Should reject for empty app server keys");
} catch (error) {
ok(error instanceof DOMException,
"Wrong exception type for empty key");
is(error.name, "InvalidAccessError",
"Wrong exception name for empty key");
}
});
add_task(function* invalidKey() {
try {
yield registration.pushManager.subscribe({
applicationServerKey: new Uint8Array([0]),
});
ok(false, "Should reject for invalid app server keys");
} catch (error) {
ok(error instanceof DOMException,
"Wrong exception type for invalid key");
is(error.name, "InvalidAccessError",
"Wrong exception name for invalid key");
}
});
add_task(function* validKey() {
var pushSubscription = yield registration.pushManager.subscribe({
applicationServerKey: yield generateKey(),
});
is(pushSubscription.endpoint, "https://example.com/push/1",
"Wrong endpoint for subscription with key");
});
add_task(function* retrieveKey() {
var pushSubscription = yield registration.pushManager.getSubscription();
is(pushSubscription.endpoint, "https://example.com/push/subWithKey",
"Got wrong endpoint for subscription with key");
isDeeply(
new Uint8Array(pushSubscription.options.applicationServerKey),
testKey,
"Got wrong app server key"
);
});
add_task(function* mismatchedKey() {
isTestingMismatchedKey = true;
try {
yield registration.pushManager.subscribe({
applicationServerKey: yield generateKey(),
});
ok(false, "Should reject for mismatched app server keys");
} catch (error) {
ok(error instanceof DOMException,
"Wrong exception type for mismatched key");
is(error.name, "InvalidStateError",
"Wrong exception name for mismatched key");
} finally {
isTestingMismatchedKey = false;
}
});
add_task(function* emptyKeyInWorker() {
var errorInfo = yield sendRequestToWorker({
type: "subscribeWithKey",
key: new ArrayBuffer(0),
});
ok(errorInfo.isDOMException,
"Wrong exception type in worker for empty key");
is(errorInfo.name, "InvalidAccessError",
"Wrong exception name in worker for empty key");
});
add_task(function* invalidKeyInWorker() {
var errorInfo = yield sendRequestToWorker({
type: "subscribeWithKey",
key: new Uint8Array([1]),
});
ok(errorInfo.isDOMException,
"Wrong exception type in worker for invalid key");
is(errorInfo.name, "InvalidAccessError",
"Wrong exception name in worker for invalid key");
});
add_task(function* validKeyInWorker() {
var key = yield generateKey();
var data = yield sendRequestToWorker({
type: "subscribeWithKey",
key: key,
});
is(data.endpoint, "https://example.com/push/2",
"Wrong endpoint for subscription with key created in worker");
isDeeply(new Uint8Array(data.key), key,
"Wrong app server key for subscription created in worker");
});
add_task(function* mismatchedKeyInWorker() {
isTestingMismatchedKey = true;
try {
var errorInfo = yield sendRequestToWorker({
type: "subscribeWithKey",
key: yield generateKey(),
});
ok(errorInfo.isDOMException,
"Wrong exception type in worker for mismatched key");
is(errorInfo.name, "InvalidStateError",
"Wrong exception name in worker for mismatched key");
} finally {
isTestingMismatchedKey = false;
}
});
add_task(function* unsubscribe() {
is(subscriptions, 2, "Wrong subscription count");
controlledFrame.remove();
});
add_task(function* unregister() {
yield registration.unregister();
});
</script>
</body>
</html>

View File

@ -73,20 +73,20 @@ function handlePush(event) {
broadcast(event, {type: "finished", okay: "no"});
}
function handleMessage(event) {
if (event.data.type == "publicKey") {
reply(event, self.registration.pushManager.getSubscription().then(
var testHandlers = {
publicKey(data) {
return self.registration.pushManager.getSubscription().then(
subscription => ({
p256dh: subscription.getKey("p256dh"),
auth: subscription.getKey("auth"),
})
));
return;
}
if (event.data.type == "resubscribe") {
reply(event, self.registration.pushManager.getSubscription().then(
);
},
resubscribe(data) {
return self.registration.pushManager.getSubscription().then(
subscription => {
assert(subscription.endpoint == event.data.endpoint,
assert(subscription.endpoint == data.endpoint,
"Wrong push endpoint in worker");
return subscription.unsubscribe();
}
@ -100,11 +100,11 @@ function handleMessage(event) {
return {
endpoint: subscription.endpoint,
};
}));
return;
}
if (event.data.type == "denySubscribe") {
reply(event, self.registration.pushManager.getSubscription().then(
});
},
denySubscribe(data) {
return self.registration.pushManager.getSubscription().then(
subscription => {
assert(!subscription,
"Should not return worker subscription with revoked permission");
@ -117,11 +117,34 @@ function handleMessage(event) {
};
});
}
));
return;
);
},
subscribeWithKey(data) {
return self.registration.pushManager.subscribe({
applicationServerKey: data.key,
}).then(subscription => {
return {
endpoint: subscription.endpoint,
key: subscription.options.applicationServerKey,
};
}, error => {
return {
isDOMException: error instanceof DOMException,
name: error.name,
};
});
},
};
function handleMessage(event) {
var handler = testHandlers[event.data.type];
if (handler) {
reply(event, handler(event.data));
} else {
reply(event, Promise.reject(
"Invalid message type: " + event.data.type));
}
reply(event, Promise.reject(
"Invalid message type: " + event.data.type));
}
function handlePushSubscriptionChange(event) {

View File

@ -455,6 +455,15 @@ var setUpServiceInParent = Task.async(function* (service, db) {
}));
},
onRegister(request) {
if (request.key) {
let appServerKey = new Uint8Array(
ChromeUtils.base64URLDecode(request.key, {
padding: "require",
})
);
equal(appServerKey.length, 65, 'Wrong app server key length');
equal(appServerKey[0], 4, 'Wrong app server key format');
}
this.serverSendMsg(JSON.stringify({
messageType: 'register',
uaid: userAgentID,

View File

@ -3,10 +3,26 @@
'use strict';
Cu.importGlobalProperties(["crypto"]);
const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
var db;
function done() {
do_test_finished();
run_next_test();
}
function generateKey() {
return crypto.subtle.generateKey({
name: "ECDSA",
namedCurve: "P-256",
}, true, ["sign", "verify"]).then(cryptoKey =>
crypto.subtle.exportKey("raw", cryptoKey.publicKey)
).then(publicKey => new Uint8Array(publicKey));
}
function run_test() {
if (isParent) {
do_get_profile();
@ -40,6 +56,69 @@ add_test(function test_subscribe_success() {
);
});
add_test(function test_subscribeWithKey_error() {
do_test_pending();
let invalidKey = [0, 1];
PushServiceComponent.subscribeWithKey(
'https://example.com/sub-key/invalid',
Services.scriptSecurityManager.getSystemPrincipal(),
invalidKey.length,
invalidKey,
(result, subscription) => {
ok(!Components.isSuccessCode(result), 'Expected error creating subscription with invalid key');
equal(result, Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR, 'Wrong error code for invalid key');
strictEqual(subscription, null, 'Unexpected subscription');
do_test_finished();
run_next_test();
}
);
});
add_test(function test_subscribeWithKey_success() {
do_test_pending();
generateKey().then(key => {
PushServiceComponent.subscribeWithKey(
'https://example.com/sub-key/ok',
Services.scriptSecurityManager.getSystemPrincipal(),
key.length,
key,
(result, subscription) => {
ok(Components.isSuccessCode(result), 'Error creating subscription with key');
notStrictEqual(subscription, null, 'Expected subscription');
done();
}
);
}, error => {
ok(false, "Error generating app server key");
done();
});
});
add_test(function test_subscribeWithKey_conflict() {
do_test_pending();
generateKey().then(differentKey => {
PushServiceComponent.subscribeWithKey(
'https://example.com/sub-key/ok',
Services.scriptSecurityManager.getSystemPrincipal(),
differentKey.length,
differentKey,
(result, subscription) => {
ok(!Components.isSuccessCode(result), 'Expected error creating subscription with conflicting key');
equal(result, Cr.NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR, 'Wrong error code for mismatched key');
strictEqual(subscription, null, 'Unexpected subscription');
done();
}
);
}, error => {
ok(false, "Error generating different app server key");
done();
});
});
add_test(function test_subscribe_error() {
do_test_pending();
PushServiceComponent.subscribe(