Bug 1359279 - Renew GCM token/Push registration/FxA Registration on push registration expired. r=Grisha

MozReview-Commit-ID: HFDjBBt9CBA

--HG--
extra : rebase_source : 727caefdd3ad0fcb9383f2098a8651a6124f662f
This commit is contained in:
Edouard Oger 2017-08-23 15:41:31 -04:00
parent 89a09e8d67
commit 126bd9114d
6 changed files with 90 additions and 14 deletions

View File

@ -15,7 +15,8 @@ public class FxAccountDevice {
private static final String JSON_KEY_PUSH_CALLBACK = "pushCallback"; private static final String JSON_KEY_PUSH_CALLBACK = "pushCallback";
private static final String JSON_KEY_PUSH_PUBLICKEY = "pushPublicKey"; private static final String JSON_KEY_PUSH_PUBLICKEY = "pushPublicKey";
private static final String JSON_KEY_PUSH_AUTHKEY = "pushAuthKey"; private static final String JSON_KEY_PUSH_AUTHKEY = "pushAuthKey";
private static final String JSON_LAST_ACCESS_TIME = "lastAccessTime"; private static final String JSON_KEY_LAST_ACCESS_TIME = "lastAccessTime";
private static final String JSON_KEY_PUSH_ENDPOINT_EXPIRED = "pushEndpointExpired";
public final String id; public final String id;
public final String name; public final String name;
@ -25,10 +26,11 @@ public class FxAccountDevice {
public final String pushCallback; public final String pushCallback;
public final String pushPublicKey; public final String pushPublicKey;
public final String pushAuthKey; public final String pushAuthKey;
public final Boolean pushEndpointExpired;
public FxAccountDevice(String name, String id, String type, Boolean isCurrentDevice, public FxAccountDevice(String name, String id, String type, Boolean isCurrentDevice,
Long lastAccessTime, String pushCallback, String pushPublicKey, Long lastAccessTime, String pushCallback, String pushPublicKey,
String pushAuthKey) { String pushAuthKey, Boolean pushEndpointExpired) {
this.name = name; this.name = name;
this.id = id; this.id = id;
this.type = type; this.type = type;
@ -37,6 +39,7 @@ public class FxAccountDevice {
this.pushCallback = pushCallback; this.pushCallback = pushCallback;
this.pushPublicKey = pushPublicKey; this.pushPublicKey = pushPublicKey;
this.pushAuthKey = pushAuthKey; this.pushAuthKey = pushAuthKey;
this.pushEndpointExpired = pushEndpointExpired;
} }
public static FxAccountDevice fromJson(ExtendedJSONObject json) { public static FxAccountDevice fromJson(ExtendedJSONObject json) {
@ -44,12 +47,24 @@ public class FxAccountDevice {
final String id = json.getString(JSON_KEY_ID); final String id = json.getString(JSON_KEY_ID);
final String type = json.getString(JSON_KEY_TYPE); final String type = json.getString(JSON_KEY_TYPE);
final Boolean isCurrentDevice = json.getBoolean(JSON_KEY_ISCURRENTDEVICE); final Boolean isCurrentDevice = json.getBoolean(JSON_KEY_ISCURRENTDEVICE);
final Long lastAccessTime = json.getLong(JSON_LAST_ACCESS_TIME); final Long lastAccessTime = json.getLong(JSON_KEY_LAST_ACCESS_TIME);
final String pushCallback = json.getString(JSON_KEY_PUSH_CALLBACK); final String pushCallback = json.getString(JSON_KEY_PUSH_CALLBACK);
final String pushPublicKey = json.getString(JSON_KEY_PUSH_PUBLICKEY); final String pushPublicKey = json.getString(JSON_KEY_PUSH_PUBLICKEY);
final String pushAuthKey = json.getString(JSON_KEY_PUSH_AUTHKEY); final String pushAuthKey = json.getString(JSON_KEY_PUSH_AUTHKEY);
// The FxA server sends this boolean as a number (bug):
// https://github.com/mozilla/fxa-auth-server/pull/2122
// Use getBoolean directly once the fix is deployed (probably ~Oct-Nov 2017).
final Object pushEndpointExpiredRaw = json.get(JSON_KEY_PUSH_ENDPOINT_EXPIRED);
final Boolean pushEndpointExpired;
if (pushEndpointExpiredRaw instanceof Number) {
pushEndpointExpired = ((Number) pushEndpointExpiredRaw).intValue() == 1;
} else if (pushEndpointExpiredRaw instanceof Boolean) {
pushEndpointExpired = (Boolean) pushEndpointExpiredRaw;
} else {
pushEndpointExpired = false;
}
return new FxAccountDevice(name, id, type, isCurrentDevice, lastAccessTime, pushCallback, return new FxAccountDevice(name, id, type, isCurrentDevice, lastAccessTime, pushCallback,
pushPublicKey, pushAuthKey); pushPublicKey, pushAuthKey, pushEndpointExpired);
} }
public ExtendedJSONObject toJson() { public ExtendedJSONObject toJson() {
@ -109,7 +124,7 @@ public class FxAccountDevice {
public FxAccountDevice build() { public FxAccountDevice build() {
return new FxAccountDevice(this.name, this.id, this.type, null, null, return new FxAccountDevice(this.name, this.id, this.type, null, null,
this.pushCallback, this.pushPublicKey, this.pushAuthKey); this.pushCallback, this.pushPublicKey, this.pushAuthKey, null);
} }
} }
} }

View File

@ -6,6 +6,8 @@ package org.mozilla.gecko.fxa.devices;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
@ -19,7 +21,12 @@ import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserContract.RemoteDevices; import org.mozilla.gecko.db.BrowserContract.RemoteDevices;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.fxa.login.State; import org.mozilla.gecko.fxa.login.State;
import org.mozilla.gecko.util.ThreadUtils;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.GeneralSecurityException;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
public class FxAccountDeviceListUpdater implements FxAccountClient20.RequestDelegate<FxAccountDevice[]> { public class FxAccountDeviceListUpdater implements FxAccountClient20.RequestDelegate<FxAccountDevice[]> {
@ -27,6 +34,10 @@ public class FxAccountDeviceListUpdater implements FxAccountClient20.RequestDele
private final AndroidFxAccount fxAccount; private final AndroidFxAccount fxAccount;
private final ContentResolver contentResolver; private final ContentResolver contentResolver;
private boolean localDevicePushEndpointExpired = false;
private final static String SYNC_PREFS_PUSH_LAST_RENEW_REGISTRATION_MS = "push.lastRenewRegistration";
private final static long TIME_BETWEEN_RENEW_REGISTRATION_MS = 2 * 7 * 24 * 3600 * 1000;
public FxAccountDeviceListUpdater(final AndroidFxAccount fxAccount, final ContentResolver cr) { public FxAccountDeviceListUpdater(final AndroidFxAccount fxAccount, final ContentResolver cr) {
this.fxAccount = fxAccount; this.fxAccount = fxAccount;
@ -46,6 +57,9 @@ public class FxAccountDeviceListUpdater implements FxAccountClient20.RequestDele
final long now = System.currentTimeMillis(); final long now = System.currentTimeMillis();
for (int i = 0; i < result.length; i++) { for (int i = 0; i < result.length; i++) {
final FxAccountDevice fxADevice = result[i]; final FxAccountDevice fxADevice = result[i];
if (fxADevice.isCurrentDevice && fxADevice.pushEndpointExpired) {
this.localDevicePushEndpointExpired = true;
}
final ContentValues deviceValues = new ContentValues(); final ContentValues deviceValues = new ContentValues();
deviceValues.put(RemoteDevices.GUID, fxADevice.id); deviceValues.put(RemoteDevices.GUID, fxADevice.id);
deviceValues.put(RemoteDevices.TYPE, fxADevice.type); deviceValues.put(RemoteDevices.TYPE, fxADevice.type);
@ -53,9 +67,7 @@ public class FxAccountDeviceListUpdater implements FxAccountClient20.RequestDele
deviceValues.put(RemoteDevices.IS_CURRENT_DEVICE, fxADevice.isCurrentDevice); deviceValues.put(RemoteDevices.IS_CURRENT_DEVICE, fxADevice.isCurrentDevice);
deviceValues.put(RemoteDevices.DATE_CREATED, now); deviceValues.put(RemoteDevices.DATE_CREATED, now);
deviceValues.put(RemoteDevices.DATE_MODIFIED, now); deviceValues.put(RemoteDevices.DATE_MODIFIED, now);
// TODO: Remove that line once FxA sends lastAccessTime all the time. deviceValues.put(RemoteDevices.LAST_ACCESS_TIME, fxADevice.lastAccessTime);
final Long lastAccessTime = fxADevice.lastAccessTime != null ? fxADevice.lastAccessTime : 0;
deviceValues.put(RemoteDevices.LAST_ACCESS_TIME, lastAccessTime);
insertValues[i] = deviceValues; insertValues[i] = deviceValues;
} }
valuesBundle.putParcelableArray(BrowserContract.METHOD_PARAM_DATA, insertValues); valuesBundle.putParcelableArray(BrowserContract.METHOD_PARAM_DATA, insertValues);
@ -108,4 +120,53 @@ public class FxAccountDeviceListUpdater implements FxAccountClient20.RequestDele
final FxAccountClient fxaClient = getSynchronousFxaClient(); final FxAccountClient fxaClient = getSynchronousFxaClient();
fxaClient.deviceList(sessionToken, this); fxaClient.deviceList(sessionToken, this);
} }
// Updates the list of remote devices, and also renews our push registration if the list provider
// tells us it's expired.
public void updateAndMaybeRenewRegistration(final Context context) {
// Synchronous operation, the re-registration will happen right after the refresh if necessary.
this.update();
if (!this.localDevicePushEndpointExpired) {
return;
}
final SharedPreferences syncPrefs;
try {
syncPrefs = fxAccount.getSyncPrefs();
} catch (UnsupportedEncodingException | GeneralSecurityException e) {
Log.e(LOG_TAG, "Could not get sync preferences, skipping push endpoint re-registration.");
return;
}
final long lastTryMs = syncPrefs.getLong(SYNC_PREFS_PUSH_LAST_RENEW_REGISTRATION_MS, 0);
final long nowMs = System.currentTimeMillis();
if (nowMs - lastTryMs < TIME_BETWEEN_RENEW_REGISTRATION_MS) {
Log.w(LOG_TAG, "Last renew registration too close, skipping.");
return;
}
final SharedPreferences.Editor syncPrefsEditor = syncPrefs.edit();
syncPrefsEditor.putLong(SYNC_PREFS_PUSH_LAST_RENEW_REGISTRATION_MS, nowMs);
syncPrefsEditor.commit();
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
try {
FxAccountDeviceListUpdater.this.renewPushRegistration(context);
} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
Log.e(LOG_TAG, "Could not renew push registration, continuing anyway", e);
}
FxAccountDeviceRegistrator.renewRegistration(context);
}
});
}
private void renewPushRegistration(Context context) throws ClassNotFoundException, NoSuchMethodException,
InvocationTargetException, IllegalAccessException {
final Class<?> pushService = Class.forName("org.mozilla.gecko.push.PushService");
final Method getInstance = pushService.getMethod("getInstance", Context.class);
final Object instance = getInstance.invoke(null, context);
final Method onRefresh = pushService.getMethod("onRefresh");
onRefresh.invoke(instance);
}
} }

View File

@ -380,7 +380,7 @@ public class FxAccountDeviceRegistrator implements BundleEventListener {
final FxAccountDevice updatedDevice = new FxAccountDevice(device.name, fxaDevice.id, device.type, final FxAccountDevice updatedDevice = new FxAccountDevice(device.name, fxaDevice.id, device.type,
null, null, null, null,
device.pushCallback, device.pushPublicKey, device.pushCallback, device.pushPublicKey,
device.pushAuthKey); device.pushAuthKey, null);
doFxaRegistration(context, fxAccount, updatedDevice, false); doFxaRegistration(context, fxAccount, updatedDevice, false);
return; return;
} }

View File

@ -484,7 +484,7 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
FxAccountDeviceListUpdater deviceListUpdater = new FxAccountDeviceListUpdater(fxAccount, context.getContentResolver()); FxAccountDeviceListUpdater deviceListUpdater = new FxAccountDeviceListUpdater(fxAccount, context.getContentResolver());
// Since the clients stage requires a fresh list of remote devices, we update the device list synchronously. // Since the clients stage requires a fresh list of remote devices, we update the device list synchronously.
deviceListUpdater.update(); deviceListUpdater.updateAndMaybeRenewRegistration(context);
} }
/** /**

View File

@ -81,9 +81,9 @@ public class TestFxAccountDeviceListUpdater {
public void testSuccessHandler() throws Throwable { public void testSuccessHandler() throws Throwable {
FxAccountDevice[] result = new FxAccountDevice[2]; FxAccountDevice[] result = new FxAccountDevice[2];
FxAccountDevice device1 = new FxAccountDevice("Current device", "deviceid1", "mobile", true, System.currentTimeMillis(), FxAccountDevice device1 = new FxAccountDevice("Current device", "deviceid1", "mobile", true, System.currentTimeMillis(),
"https://localhost/push/callback1", "abc123", "321cba"); "https://localhost/push/callback1", "abc123", "321cba", false);
FxAccountDevice device2 = new FxAccountDevice("Desktop PC", "deviceid2", "desktop", true, System.currentTimeMillis(), FxAccountDevice device2 = new FxAccountDevice("Desktop PC", "deviceid2", "desktop", true, System.currentTimeMillis(),
"https://localhost/push/callback2", "abc123", "321cba"); "https://localhost/push/callback2", "abc123", "321cba", false);
result[0] = device1; result[0] = device1;
result[1] = device2; result[1] = device2;

View File

@ -180,7 +180,7 @@ public class MockFxAccountClient implements FxAccountClient {
String deviceId = deviceToRegister.id; String deviceId = deviceToRegister.id;
if (TextUtils.isEmpty(deviceId)) { // Create if (TextUtils.isEmpty(deviceId)) { // Create
deviceId = UUID.randomUUID().toString(); deviceId = UUID.randomUUID().toString();
FxAccountDevice device = new FxAccountDevice(deviceToRegister.name, deviceId, deviceToRegister.type, null, null, null, null, null); FxAccountDevice device = new FxAccountDevice(deviceToRegister.name, deviceId, deviceToRegister.type, null, null, null, null, null, false);
requestDelegate.handleSuccess(device); requestDelegate.handleSuccess(device);
} else { // Update } else { // Update
FxAccountDevice existingDevice = user.devices.get(deviceId); FxAccountDevice existingDevice = user.devices.get(deviceId);
@ -190,7 +190,7 @@ public class MockFxAccountClient implements FxAccountClient {
deviceName = deviceToRegister.name; deviceName = deviceToRegister.name;
} // We could also update the other fields.. } // We could also update the other fields..
FxAccountDevice device = new FxAccountDevice(deviceName, existingDevice.id, existingDevice.type, existingDevice.isCurrentDevice, FxAccountDevice device = new FxAccountDevice(deviceName, existingDevice.id, existingDevice.type, existingDevice.isCurrentDevice,
existingDevice.lastAccessTime, existingDevice.pushCallback, existingDevice.pushPublicKey,existingDevice.pushAuthKey); existingDevice.lastAccessTime, existingDevice.pushCallback, existingDevice.pushPublicKey,existingDevice.pushAuthKey, false);
requestDelegate.handleSuccess(device); requestDelegate.handleSuccess(device);
} else { // Device unknown } else { // Device unknown
handleFailure(requestDelegate, HttpStatus.SC_BAD_REQUEST, FxAccountRemoteError.UNKNOWN_DEVICE, "device is unknown"); handleFailure(requestDelegate, HttpStatus.SC_BAD_REQUEST, FxAccountRemoteError.UNKNOWN_DEVICE, "device is unknown");