Bug 1254643 - Delete FxA device when Fennec Firefox Account is removed. r=Grisha

MozReview-Commit-ID: H4lJlXGYIBg

--HG--
extra : rebase_source : f5862b9591def7ee300c6f02ff7750ead043241a
This commit is contained in:
Edouard Oger 2017-03-13 14:35:41 -04:00
Родитель d1865bc01a
Коммит ec0e5f70b8
6 изменённых файлов: 111 добавлений и 1 удалений

Просмотреть файл

@ -19,6 +19,7 @@ public interface FxAccountClient {
public void keys(byte[] keyFetchToken, RequestDelegate<TwoKeys> requestDelegate); public void keys(byte[] keyFetchToken, RequestDelegate<TwoKeys> requestDelegate);
public void sign(byte[] sessionToken, ExtendedJSONObject publicKey, long certificateDurationInMilliseconds, RequestDelegate<String> requestDelegate); public void sign(byte[] sessionToken, ExtendedJSONObject publicKey, long certificateDurationInMilliseconds, RequestDelegate<String> requestDelegate);
public void registerOrUpdateDevice(byte[] sessionToken, FxAccountDevice device, RequestDelegate<FxAccountDevice> requestDelegate); public void registerOrUpdateDevice(byte[] sessionToken, FxAccountDevice device, RequestDelegate<FxAccountDevice> requestDelegate);
public void destroyDevice(byte[] sessionToken, String deviceId, RequestDelegate<ExtendedJSONObject> requestDelegate);
public void deviceList(byte[] sessionToken, RequestDelegate<FxAccountDevice[]> requestDelegate); public void deviceList(byte[] sessionToken, RequestDelegate<FxAccountDevice[]> requestDelegate);
public void notifyDevices(byte[] sessionToken, List<String> deviceIds, ExtendedJSONObject payload, Long TTL, RequestDelegate<ExtendedJSONObject> requestDelegate); public void notifyDevices(byte[] sessionToken, List<String> deviceIds, ExtendedJSONObject payload, Long TTL, RequestDelegate<ExtendedJSONObject> requestDelegate);
} }

Просмотреть файл

@ -822,6 +822,42 @@ public class FxAccountClient20 implements FxAccountClient {
post(resource, body); post(resource, body);
} }
@Override
public void destroyDevice(byte[] sessionToken, String deviceId, RequestDelegate<ExtendedJSONObject> delegate) {
final byte[] tokenId = new byte[32];
final byte[] reqHMACKey = new byte[32];
final byte[] requestKey = new byte[32];
try {
HKDF.deriveMany(sessionToken, new byte[0], FxAccountUtils.KW("sessionToken"), tokenId, reqHMACKey, requestKey);
} catch (Exception e) {
invokeHandleError(delegate, e);
return;
}
final BaseResource resource;
final ExtendedJSONObject body = new ExtendedJSONObject();
body.put("id", deviceId);
try {
resource = getBaseResource("account/device/destroy");
} catch (URISyntaxException | UnsupportedEncodingException e) {
invokeHandleError(delegate, e);
return;
}
resource.delegate = new ResourceDelegate<ExtendedJSONObject>(resource, delegate, ResponseType.JSON_OBJECT, tokenId, reqHMACKey) {
@Override
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
try {
delegate.handleSuccess(body);
} catch (Exception e) {
delegate.handleError(e);
}
}
};
post(resource, body);
}
@Override @Override
public void deviceList(byte[] sessionToken, RequestDelegate<FxAccountDevice[]> delegate) { public void deviceList(byte[] sessionToken, RequestDelegate<FxAccountDevice[]> delegate) {
final byte[] tokenId = new byte[32]; final byte[] tokenId = new byte[32];

Просмотреть файл

@ -52,6 +52,9 @@ public class FxAccountConstants {
public static final String ACCOUNT_DELETED_INTENT_ACCOUNT_PROFILE = "account_deleted_intent_profile"; public static final String ACCOUNT_DELETED_INTENT_ACCOUNT_PROFILE = "account_deleted_intent_profile";
public static final String ACCOUNT_OAUTH_SERVICE_ENDPOINT_KEY = "account_oauth_service_endpoint"; public static final String ACCOUNT_OAUTH_SERVICE_ENDPOINT_KEY = "account_oauth_service_endpoint";
public static final String ACCOUNT_DELETED_INTENT_ACCOUNT_AUTH_TOKENS = "account_deleted_intent_auth_tokens"; public static final String ACCOUNT_DELETED_INTENT_ACCOUNT_AUTH_TOKENS = "account_deleted_intent_auth_tokens";
public static final String ACCOUNT_DELETED_INTENT_ACCOUNT_SESSION_TOKEN = "account_deleted_intent_session_token";
public static final String ACCOUNT_DELETED_INTENT_ACCOUNT_SERVER_URI = "account_deleted_intent_account_server_uri";
public static final String ACCOUNT_DELETED_INTENT_ACCOUNT_DEVICE_ID = "account_deleted_intent_account_device_id";
/** /**
* This action is broadcast when an Android Firefox Account's internal state * This action is broadcast when an Android Firefox Account's internal state

Просмотреть файл

@ -693,6 +693,15 @@ public class AndroidFxAccount {
intent.putExtra(FxAccountConstants.ACCOUNT_OAUTH_SERVICE_ENDPOINT_KEY, getOAuthServerURI()); intent.putExtra(FxAccountConstants.ACCOUNT_OAUTH_SERVICE_ENDPOINT_KEY, getOAuthServerURI());
// Deleted broadcasts are package-private, so there's no security risk include the tokens in the extras // Deleted broadcasts are package-private, so there's no security risk include the tokens in the extras
intent.putExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_AUTH_TOKENS, tokens.toArray(new String[tokens.size()])); intent.putExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_AUTH_TOKENS, tokens.toArray(new String[tokens.size()]));
try {
intent.putExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_SESSION_TOKEN, getSessionToken());
} catch (InvalidFxAState e) {
Logger.warn(LOG_TAG, "Could not get a session token, ignoring.", e);
}
intent.putExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_SERVER_URI, getAccountServerURI());
intent.putExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_DEVICE_ID, getDeviceId());
return intent; return intent;
} }

Просмотреть файл

@ -7,8 +7,11 @@ package org.mozilla.gecko.fxa.receivers;
import android.app.IntentService; import android.app.IntentService;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.text.TextUtils;
import org.mozilla.gecko.background.common.log.Logger; import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.fxa.FxAccountClient20;
import org.mozilla.gecko.background.fxa.FxAccountClientException;
import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClient; import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClient;
import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClientException.FxAccountAbstractClientRemoteException; import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClientException.FxAccountAbstractClientRemoteException;
import org.mozilla.gecko.background.fxa.oauth.FxAccountOAuthClient10; import org.mozilla.gecko.background.fxa.oauth.FxAccountOAuthClient10;
@ -16,10 +19,13 @@ import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.fxa.sync.FxAccountNotificationManager; import org.mozilla.gecko.fxa.sync.FxAccountNotificationManager;
import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter; import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.repositories.android.ClientsDatabase; import org.mozilla.gecko.sync.repositories.android.ClientsDatabase;
import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository; import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/** /**
* A background service to clean up after a Firefox Account is deleted. * A background service to clean up after a Firefox Account is deleted.
@ -69,6 +75,8 @@ public class FxAccountDeletedService extends IntentService {
return; return;
} }
// Delete current device the from FxA devices list.
deleteFxADevice(intent);
// Fire up gecko and unsubscribe push // Fire up gecko and unsubscribe push
final Intent geckoIntent = new Intent(); final Intent geckoIntent = new Intent();
@ -76,7 +84,6 @@ public class FxAccountDeletedService extends IntentService {
geckoIntent.setClassName(context, "org.mozilla.gecko.GeckoService"); geckoIntent.setClassName(context, "org.mozilla.gecko.GeckoService");
geckoIntent.putExtra("category", "android-push-service"); geckoIntent.putExtra("category", "android-push-service");
geckoIntent.putExtra("data", "android-fxa-unsubscribe"); geckoIntent.putExtra("data", "android-fxa-unsubscribe");
final AndroidFxAccount fxAccount = AndroidFxAccount.fromContext(context);
geckoIntent.putExtra("org.mozilla.gecko.intent.PROFILE_NAME", geckoIntent.putExtra("org.mozilla.gecko.intent.PROFILE_NAME",
intent.getStringExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_PROFILE)); intent.getStringExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_PROFILE));
context.startService(geckoIntent); context.startService(geckoIntent);
@ -151,4 +158,38 @@ public class FxAccountDeletedService extends IntentService {
Logger.error(LOG_TAG, "Cached OAuth server URI is null or cached OAuth tokens are null; ignoring."); Logger.error(LOG_TAG, "Cached OAuth server URI is null or cached OAuth tokens are null; ignoring.");
} }
} }
// Remove our current device from the FxA device list.
private void deleteFxADevice(Intent intent) {
final byte[] sessionToken = intent.getByteArrayExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_SESSION_TOKEN);
if (sessionToken == null) {
Logger.warn(LOG_TAG, "Empty session token, skipping FxA device destruction.");
return;
}
final String deviceId = intent.getStringExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_DEVICE_ID);
if (TextUtils.isEmpty(deviceId)) {
Logger.warn(LOG_TAG, "Empty FxA device ID, skipping FxA device destruction.");
return;
}
ExecutorService executor = Executors.newSingleThreadExecutor(); // Not called often, it's okay to spawn another thread
final String accountServerURI = intent.getStringExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_SERVER_URI);
final FxAccountClient20 fxAccountClient = new FxAccountClient20(accountServerURI, executor);
fxAccountClient.destroyDevice(sessionToken, deviceId, new FxAccountClient20.RequestDelegate<ExtendedJSONObject>() {
@Override
public void handleError(Exception e) {
Logger.error(LOG_TAG, "Error while trying to delete the FxA device; ignoring.", e);
}
@Override
public void handleFailure(FxAccountClientException.FxAccountClientRemoteException e) {
Logger.error(LOG_TAG, "Exception while trying to delete the FxA device; ignoring.", e);
}
@Override
public void handleSuccess(ExtendedJSONObject result) {
Logger.info(LOG_TAG, "Successfully deleted the FxA device.");
}
});
}
} }

Просмотреть файл

@ -202,6 +202,26 @@ public class MockFxAccountClient implements FxAccountClient {
} }
} }
@Override
public void destroyDevice(byte[] sessionToken, String deviceId, RequestDelegate<ExtendedJSONObject> requestDelegate) {
String email = sessionTokens.get(Utils.byte2Hex(sessionToken));
User user = users.get(email);
if (email == null || user == null) {
handleFailure(requestDelegate, HttpStatus.SC_UNAUTHORIZED, FxAccountRemoteError.INVALID_AUTHENTICATION_TOKEN, "invalid sessionToken");
return;
}
if (!user.verified) {
handleFailure(requestDelegate, HttpStatus.SC_BAD_REQUEST, FxAccountRemoteError.ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT, "user is unverified");
return;
}
if(user.devices.containsKey(deviceId)) {
user.devices.remove(deviceId);
requestDelegate.handleSuccess(new ExtendedJSONObject());
} else {
handleFailure(requestDelegate, HttpStatus.SC_BAD_REQUEST, FxAccountRemoteError.UNKNOWN_DEVICE, "device is unknown");
}
}
@Override @Override
public void deviceList(byte[] sessionToken, RequestDelegate<FxAccountDevice[]> requestDelegate) { public void deviceList(byte[] sessionToken, RequestDelegate<FxAccountDevice[]> requestDelegate) {
String email = sessionTokens.get(Utils.byte2Hex(sessionToken)); String email = sessionTokens.get(Utils.byte2Hex(sessionToken));