Bug 1265593 - Forward app server keys to Autopush on Android. r=nalexander

MozReview-Commit-ID: 3J4mM1k0pcY

--HG--
extra : rebase_source : c5a33f2f7043321307da17a6915dedfac66f1fc9
extra : histedit_source : 0357740fc62df416635c90a1ad075f6ee5e492e8
This commit is contained in:
Kit Cambridge 2016-03-22 12:09:31 -07:00
parent 978954c755
commit 02d8a1e5d9
7 changed files with 56 additions and 14 deletions

View File

@ -208,9 +208,15 @@ this.PushServiceAndroidGCM = {
register: function(record) {
console.debug("register:", record);
let ctime = Date.now();
let appServerKey = record.appServerKey ?
ChromeUtils.base64URLEncode(record.appServerKey, {
// The Push server requires padding.
pad: true,
}) : null;
// Caller handles errors.
return Messaging.sendRequestForResult({
type: "PushServiceAndroidGCM:SubscribeChannel",
appServerKey: appServerKey,
}).then(data => {
console.debug("Got data:", data);
return PushCrypto.generateKeys()
@ -227,6 +233,7 @@ this.PushServiceAndroidGCM = {
p256dhPublicKey: exportedKeys[0],
p256dhPrivateKey: exportedKeys[1],
authenticationSecret: PushCrypto.generateAuthenticationSecret(),
appServerKey: record.appServerKey,
})
);
});

View File

@ -6,6 +6,7 @@
package org.mozilla.gecko.push;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.mozilla.gecko.push.RegisterUserAgentResponse;
import org.mozilla.gecko.push.SubscribeChannelResponse;
@ -95,9 +96,9 @@ public class PushClient {
delegate.responseOrThrow(); // For side-effects only.
}
public SubscribeChannelResponse subscribeChannel(@NonNull String uaid, @NonNull String secret) throws LocalException, AutopushClientException {
public SubscribeChannelResponse subscribeChannel(@NonNull String uaid, @NonNull String secret, @Nullable String appServerKey) throws LocalException, AutopushClientException {
final Delegate<SubscribeChannelResponse> delegate = new Delegate<>();
autopushClient.subscribeChannel(uaid, secret, delegate);
autopushClient.subscribeChannel(uaid, secret, appServerKey, delegate);
return delegate.responseOrThrow();
}

View File

@ -6,6 +6,7 @@
package org.mozilla.gecko.push;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import org.json.JSONObject;
@ -101,14 +102,14 @@ public class PushManager {
return registration;
}
public PushSubscription subscribeChannel(final @NonNull String profileName, final @NonNull String service, final @NonNull JSONObject serviceData, final long now) throws ProfileNeedsConfigurationException, AutopushClientException, PushClient.LocalException, GcmTokenClient.NeedsGooglePlayServicesException, IOException {
public PushSubscription subscribeChannel(final @NonNull String profileName, final @NonNull String service, final @NonNull JSONObject serviceData, @Nullable String appServerKey, final long now) throws ProfileNeedsConfigurationException, AutopushClientException, PushClient.LocalException, GcmTokenClient.NeedsGooglePlayServicesException, IOException {
Log.i(LOG_TAG, "Subscribing to channel for service: " + service + "; for profile named: " + profileName);
final PushRegistration registration = advanceRegistration(profileName, now);
final PushSubscription subscription = subscribeChannel(registration, profileName, service, serviceData, System.currentTimeMillis());
final PushSubscription subscription = subscribeChannel(registration, profileName, service, serviceData, appServerKey, System.currentTimeMillis());
return subscription;
}
protected PushSubscription subscribeChannel(final @NonNull PushRegistration registration, final @NonNull String profileName, final @NonNull String service, final @NonNull JSONObject serviceData, final long now) throws AutopushClientException, PushClient.LocalException {
protected PushSubscription subscribeChannel(final @NonNull PushRegistration registration, final @NonNull String profileName, final @NonNull String service, final @NonNull JSONObject serviceData, @Nullable String appServerKey, final long now) throws AutopushClientException, PushClient.LocalException {
final String uaid = registration.uaid.value;
final String secret = registration.secret;
if (uaid == null || secret == null) {
@ -118,7 +119,7 @@ public class PushManager {
// Verify endpoint is not null?
final PushClient pushClient = pushClientFactory.getPushClient(registration.autopushEndpoint, registration.debug);
final SubscribeChannelResponse result = pushClient.subscribeChannel(uaid, secret);
final SubscribeChannelResponse result = pushClient.subscribeChannel(uaid, secret, appServerKey);
if (registration.debug) {
Log.i(LOG_TAG, "Got chid: " + result.channelID + " and endpoint: " + result.endpoint);
} else {

View File

@ -322,6 +322,7 @@ public class PushService implements BundleEventListener {
if ("PushServiceAndroidGCM:SubscribeChannel".equals(event)) {
final String service = SERVICE_WEBPUSH;
final JSONObject serviceData;
final String appServerKey = message.getString("appServerKey");
try {
serviceData = new JSONObject();
serviceData.put("profileName", geckoProfile.getName());
@ -334,7 +335,7 @@ public class PushService implements BundleEventListener {
final PushSubscription subscription;
try {
subscription = pushManager.subscribeChannel(geckoProfile.getName(), service, serviceData, System.currentTimeMillis());
subscription = pushManager.subscribeChannel(geckoProfile.getName(), service, serviceData, appServerKey, System.currentTimeMillis());
} catch (PushManager.ProfileNeedsConfigurationException | AutopushClientException | PushClient.LocalException | IOException e) {
Log.e(LOG_TAG, "Got exception in " + event, e);
callback.sendError("Got exception handling message [" + event + "]: " + e.toString());

View File

@ -340,7 +340,7 @@ public class AutopushClient {
}
public void subscribeChannel(final String uaid, final String secret, RequestDelegate<SubscribeChannelResponse> delegate) {
public void subscribeChannel(final String uaid, final String secret, final String appServerKey, RequestDelegate<SubscribeChannelResponse> delegate) {
final BaseResource resource;
try {
resource = new BaseResource(new URI(serverURI + "registration/" + uaid + "/subscription"));
@ -366,6 +366,7 @@ public class AutopushClient {
};
final ExtendedJSONObject body = new ExtendedJSONObject();
body.put("key", appServerKey);
resource.post(body);
}

View File

@ -19,6 +19,7 @@ import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@ -46,7 +47,7 @@ public class TestPushManager {
doReturn(new SubscribeChannelResponse("opaque-chid", "https://localhost:8085/opaque-push-endpoint"))
.when(pushClient)
.subscribeChannel(anyString(), anyString());
.subscribeChannel(anyString(), anyString(), isNull(String.class));
PushManager.PushClientFactory pushClientFactory = mock(PushManager.PushClientFactory.class);
doReturn(pushClient).when(pushClientFactory).getPushClient(anyString(), anyBoolean());
@ -140,7 +141,7 @@ public class TestPushManager {
// We should be able to register with non-null serviceData.
final JSONObject webpushData = new JSONObject();
webpushData.put("version", 5);
PushSubscription subscription = manager.subscribeChannel("default", "webpush", webpushData, System.currentTimeMillis());
PushSubscription subscription = manager.subscribeChannel("default", "webpush", webpushData, null, System.currentTimeMillis());
assertSubscribed(subscription);
subscription = manager.registrationForSubscription(subscription.chid).getSubscription(subscription.chid);
@ -148,7 +149,7 @@ public class TestPushManager {
Assert.assertEquals(5, subscription.serviceData.get("version"));
// We should be able to register with null serviceData.
subscription = manager.subscribeChannel("default", "sync", null, System.currentTimeMillis());
subscription = manager.subscribeChannel("default", "sync", null, null, System.currentTimeMillis());
assertSubscribed(subscription);
subscription = manager.registrationForSubscription(subscription.chid).getSubscription(subscription.chid);
@ -165,7 +166,7 @@ public class TestPushManager {
// We should be able to register with non-null serviceData.
final JSONObject webpushData = new JSONObject();
webpushData.put("version", 5);
PushSubscription subscription = manager.subscribeChannel("default", "webpush", webpushData, System.currentTimeMillis());
PushSubscription subscription = manager.subscribeChannel("default", "webpush", webpushData, null, System.currentTimeMillis());
assertSubscribed(subscription);
// No exception is success.
@ -224,7 +225,7 @@ public class TestPushManager {
registration = manager.registerUserAgent("default", System.currentTimeMillis());
assertRegistered(registration, "http://localhost:8080", true);
PushSubscription subscription = manager.subscribeChannel("default", "webpush", null, System.currentTimeMillis());
PushSubscription subscription = manager.subscribeChannel("default", "webpush", null, null, System.currentTimeMillis());
assertSubscribed(subscription);
manager.startup(System.currentTimeMillis());

View File

@ -3,12 +3,17 @@
package org.mozilla.gecko.push.autopush.test;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mozilla.apache.commons.codec.binary.Base64;
import org.mozilla.gecko.background.fxa.FxAccountUtils;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.background.testhelpers.WaitHelper;
@ -20,6 +25,7 @@ import org.mozilla.gecko.push.autopush.AutopushClientException;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.net.BaseResource;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.mockito.Mockito.any;
@ -108,13 +114,14 @@ public class TestLiveAutopushClient {
// We should be able to subscribe to a channel.
final RequestDelegate<SubscribeChannelResponse> subscribeDelegate = mock(RequestDelegate.class);
client.subscribeChannel(registerResponse.uaid, registerResponse.secret, subscribeDelegate);
client.subscribeChannel(registerResponse.uaid, registerResponse.secret, null, subscribeDelegate);
final SubscribeChannelResponse subscribeResponse = assertSuccess(subscribeDelegate, SubscribeChannelResponse.class);
Assert.assertNotNull(subscribeResponse);
Assert.assertNotNull(subscribeResponse.channelID);
Assert.assertNotNull(subscribeResponse.endpoint);
Assert.assertThat(subscribeResponse.endpoint, startsWith(FxAccountUtils.getAudienceForURL(serverURL)));
Assert.assertThat(subscribeResponse.endpoint, containsString("/v1/"));
// And we should be able to unsubscribe.
final RequestDelegate<Void> unsubscribeDelegate = mock(RequestDelegate.class);
@ -122,6 +129,29 @@ public class TestLiveAutopushClient {
Assert.assertNull(assertSuccess(unsubscribeDelegate, Void.class));
// We should be able to create a restricted subscription by specifying
// an ECDSA public key using the P-256 curve.
final RequestDelegate<SubscribeChannelResponse> subscribeWithKeyDelegate = mock(RequestDelegate.class);
final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDSA");
keyPairGenerator.initialize(256);
final KeyPair keyPair = keyPairGenerator.generateKeyPair();
final PublicKey publicKey = keyPair.getPublic();
String appServerKey = Base64.encodeBase64URLSafeString(publicKey.getEncoded());
client.subscribeChannel(registerResponse.uaid, registerResponse.secret, appServerKey, subscribeWithKeyDelegate);
final SubscribeChannelResponse subscribeWithKeyResponse = assertSuccess(subscribeWithKeyDelegate, SubscribeChannelResponse.class);
Assert.assertNotNull(subscribeWithKeyResponse);
Assert.assertNotNull(subscribeWithKeyResponse.channelID);
Assert.assertNotNull(subscribeWithKeyResponse.endpoint);
Assert.assertThat(subscribeWithKeyResponse.endpoint, startsWith(FxAccountUtils.getAudienceForURL(serverURL)));
Assert.assertThat(subscribeWithKeyResponse.endpoint, containsString("/v2/"));
// And we should be able to drop the restricted subscription.
final RequestDelegate<Void> unsubscribeWithKeyDelegate = mock(RequestDelegate.class);
client.unsubscribeChannel(registerResponse.uaid, registerResponse.secret, subscribeWithKeyResponse.channelID, unsubscribeWithKeyDelegate);
Assert.assertNull(assertSuccess(unsubscribeWithKeyDelegate, Void.class));
// Trying to unsubscribe a second time should give a 410.
final RequestDelegate<Void> reunsubscribeDelegate = mock(RequestDelegate.class);
client.unsubscribeChannel(registerResponse.uaid, registerResponse.secret, subscribeResponse.channelID, reunsubscribeDelegate);