Bug 962320 - User per-profile prefs for FxA, and store token server in account object. r=nalexander

This commit is contained in:
Richard Newman 2014-01-21 21:31:02 -08:00
parent a8b7a9e312
commit 50fdd76d57
8 changed files with 111 additions and 32 deletions

View File

@ -12,9 +12,9 @@ public class FxAccountConstants {
public static final String ACCOUNT_TYPE = "@MOZ_ANDROID_SHARED_FXACCOUNT_TYPE@";
public static final String DEFAULT_IDP_ENDPOINT = "https://api-accounts-onepw.dev.lcip.org";
public static final String DEFAULT_AUTH_ENDPOINT = "http://auth.oldsync.dev.lcip.org";
public static final String PREFS_PATH = "fxa.v1";
public static final String DEFAULT_TOKEN_SERVER_ENDPOINT = "http://auth.oldsync.dev.lcip.org";
public static final String DEFAULT_TOKEN_SERVER_URI = DEFAULT_TOKEN_SERVER_ENDPOINT +
(DEFAULT_TOKEN_SERVER_ENDPOINT.endsWith("/") ? "" : "/") + "1.0/sync/1.1";
// For extra debugging. Not final so it can be changed from Fennec, or from
// an add-on.

View File

@ -162,8 +162,12 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi
Account account;
try {
final String profile = Constants.DEFAULT_PROFILE;
final String tokenServerURI = FxAccountConstants.DEFAULT_TOKEN_SERVER_URI;
account = AndroidFxAccount.addAndroidAccount(activity, email, password,
profile, serverURI, null, null, false);
profile,
serverURI,
tokenServerURI,
null, null, false);
if (account == null) {
throw new RuntimeException("XXX what?");
}

View File

@ -131,8 +131,11 @@ public class FxAccountSignInActivity extends FxAccountAbstractSetupActivity {
Account account;
try {
final String profile = Constants.DEFAULT_PROFILE;
final String tokenServerURI = FxAccountConstants.DEFAULT_TOKEN_SERVER_URI;
account = AndroidFxAccount.addAndroidAccount(activity, email, password,
serverURI, profile, result.sessionToken, result.keyFetchToken, result.verified);
serverURI,
tokenServerURI,
profile, result.sessionToken, result.keyFetchToken, result.verified);
if (account == null) {
throw new RuntimeException("XXX what?");
}

View File

@ -38,7 +38,7 @@ public interface AbstractFxAccount {
* Get the Firefox Account auth server URI that this account login flow should
* talk to.
*/
public String getServerURI();
public String getAccountServerURI();
/**
* @return the profile name associated with the account, such as "default".

View File

@ -5,10 +5,13 @@
package org.mozilla.gecko.fxa.authenticator;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collections;
import org.mozilla.gecko.background.common.GlobalConstants;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.fxa.FxAccountUtils;
import org.mozilla.gecko.browserid.BrowserIDKeyPair;
@ -33,9 +36,16 @@ import android.os.Bundle;
public class AndroidFxAccount implements AbstractFxAccount {
protected static final String LOG_TAG = AndroidFxAccount.class.getSimpleName();
public static final int CURRENT_ACCOUNT_VERSION = 2;
public static final int CURRENT_PREFS_VERSION = 1;
public static final int CURRENT_ACCOUNT_VERSION = 3;
public static final String ACCOUNT_KEY_ACCOUNT_VERSION = "version";
public static final String ACCOUNT_KEY_PROFILE = "profile";
public static final String ACCOUNT_KEY_IDP_SERVER = "idpServerURI";
// The audience should always be a prefix of the token server URI.
public static final String ACCOUNT_KEY_AUDIENCE = "audience"; // Sync-specific.
public static final String ACCOUNT_KEY_TOKEN_SERVER = "tokenServerURI"; // Sync-specific.
public static final String ACCOUNT_KEY_DESCRIPTOR = "descriptor";
public static final int CURRENT_BUNDLE_VERSION = 1;
@ -43,7 +53,6 @@ public class AndroidFxAccount implements AbstractFxAccount {
public static final String BUNDLE_KEY_ASSERTION = "assertion";
public static final String BUNDLE_KEY_CERTIFICATE = "certificate";
public static final String BUNDLE_KEY_INVALID = "invalid";
public static final String BUNDLE_KEY_SERVERURI = "serverURI";
public static final String BUNDLE_KEY_SESSION_TOKEN = "sessionToken";
public static final String BUNDLE_KEY_KEY_FETCH_TOKEN = "keyFetchToken";
public static final String BUNDLE_KEY_VERIFIED = "verified";
@ -196,6 +205,50 @@ public class AndroidFxAccount implements AbstractFxAccount {
return accountManager.getUserData(account, ACCOUNT_KEY_PROFILE);
}
@Override
public String getAccountServerURI() {
return accountManager.getUserData(account, ACCOUNT_KEY_IDP_SERVER);
}
public String getAudience() {
return accountManager.getUserData(account, ACCOUNT_KEY_AUDIENCE);
}
public String getTokenServerURI() {
return accountManager.getUserData(account, ACCOUNT_KEY_TOKEN_SERVER);
}
/**
* This needs to return a string because of the tortured prefs access in GlobalSession.
*/
public String getSyncPrefsPath() throws GeneralSecurityException, UnsupportedEncodingException {
String profile = getProfile();
String username = account.name;
if (profile == null ||
username == null) {
throw new IllegalStateException("Missing profile or username. Cannot fetch prefs.");
}
final String tokenServerURI = getTokenServerURI();
if (tokenServerURI == null) {
throw new IllegalStateException("No token server URI. Cannot fetch prefs.");
}
final String fxaServerURI = getAccountServerURI();
if (fxaServerURI == null) {
throw new IllegalStateException("No account server URI. Cannot fetch prefs.");
}
final String product = GlobalConstants.BROWSER_INTENT_PACKAGE + ".fxa";
final long version = CURRENT_PREFS_VERSION;
// This is unique for each syncing 'view' of the account.
final String serverURLThing = fxaServerURI + "!" + tokenServerURI;
return Utils.getPrefsPath(product, username, serverURLThing, profile, version);
}
@Override
public void setQuickStretchedPW(byte[] quickStretchedPW) {
accountManager.setPassword(account, quickStretchedPW == null ? null : Utils.byte2Hex(quickStretchedPW));
@ -208,11 +261,6 @@ public class AndroidFxAccount implements AbstractFxAccount {
return quickStretchedPW == null ? null : Utils.hex2Byte(quickStretchedPW);
}
@Override
public String getServerURI() {
return getBundleData(BUNDLE_KEY_SERVERURI);
}
protected byte[] getUserDataBytes(String key) {
String data = accountManager.getUserData(account, key);
if (data == null) {
@ -343,17 +391,28 @@ public class AndroidFxAccount implements AbstractFxAccount {
return o;
}
public static Account addAndroidAccount(Context context, String email, String password, String profile,
String serverURI, byte[] sessionToken, byte[] keyFetchToken, boolean verified)
throws UnsupportedEncodingException, GeneralSecurityException {
public static Account addAndroidAccount(
Context context,
String email,
String password,
String profile,
String idpServerURI,
String tokenServerURI,
byte[] sessionToken,
byte[] keyFetchToken,
boolean verified)
throws UnsupportedEncodingException, GeneralSecurityException, URISyntaxException {
if (email == null) {
throw new IllegalArgumentException("email must not be null");
}
if (password == null) {
throw new IllegalArgumentException("password must not be null");
}
if (serverURI == null) {
throw new IllegalArgumentException("serverURI must not be null");
if (idpServerURI == null) {
throw new IllegalArgumentException("idpServerURI must not be null");
}
if (tokenServerURI == null) {
throw new IllegalArgumentException("tokenServerURI must not be null");
}
// sessionToken and keyFetchToken are allowed to be null; they can be
// fetched via /account/login from the password. These tokens are generated
@ -371,10 +430,12 @@ public class AndroidFxAccount implements AbstractFxAccount {
Bundle userdata = new Bundle();
userdata.putInt(ACCOUNT_KEY_ACCOUNT_VERSION, CURRENT_ACCOUNT_VERSION);
userdata.putString(ACCOUNT_KEY_IDP_SERVER, idpServerURI);
userdata.putString(ACCOUNT_KEY_TOKEN_SERVER, tokenServerURI);
userdata.putString(ACCOUNT_KEY_AUDIENCE, computeAudience(tokenServerURI));
ExtendedJSONObject descriptor = new ExtendedJSONObject();
descriptor.put(BUNDLE_KEY_BUNDLE_VERSION, CURRENT_BUNDLE_VERSION);
descriptor.put(BUNDLE_KEY_SERVERURI, serverURI);
descriptor.put(BUNDLE_KEY_SESSION_TOKEN, sessionToken == null ? null : Utils.byte2Hex(sessionToken));
descriptor.put(BUNDLE_KEY_KEY_FETCH_TOKEN, keyFetchToken == null ? null : Utils.byte2Hex(keyFetchToken));
descriptor.put(BUNDLE_KEY_VERIFIED, Boolean.valueOf(verified).toString());
@ -392,6 +453,12 @@ public class AndroidFxAccount implements AbstractFxAccount {
return account;
}
// TODO: this is shit.
private static String computeAudience(String tokenServerURI) throws URISyntaxException {
URI uri = new URI(tokenServerURI);
return new URI(uri.getScheme(), uri.getHost(), null, null).toString();
}
@Override
public boolean isValid() {
// Boolean.valueOf only returns true for the string "true"; this errors in

View File

@ -56,7 +56,7 @@ public class FxAccountAuthenticator extends AbstractAccountAuthenticator {
userData.putString(JSON_KEY_KA, kA);
userData.putString(JSON_KEY_KB, kB);
userData.putString(JSON_KEY_IDP_ENDPOINT, FxAccountConstants.DEFAULT_IDP_ENDPOINT);
userData.putString(JSON_KEY_AUTH_ENDPOINT, FxAccountConstants.DEFAULT_AUTH_ENDPOINT);
userData.putString(JSON_KEY_AUTH_ENDPOINT, FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT);
if (!accountManager.addAccountExplicitly(account, sessionToken, userData)) {
Logger.warn(LOG_TAG, "Error adding account named " + account.name + " of type " + account.type);
return null;

View File

@ -54,7 +54,7 @@ public class FxAccountLoginPolicy {
}
protected FxAccountClient makeFxAccountClient() {
String serverURI = fxAccount.getServerURI();
String serverURI = fxAccount.getAccountServerURI();
return new FxAccountClient20(serverURI, executor);
}
@ -101,7 +101,7 @@ public class FxAccountLoginPolicy {
};
public AccountState getAccountState(AbstractFxAccount fxAccount) {
String serverURI = fxAccount.getServerURI();
String serverURI = fxAccount.getAccountServerURI();
byte[] emailUTF8 = fxAccount.getEmailUTF8();
byte[] quickStretchedPW = fxAccount.getQuickStretchedPW();
if (!fxAccount.isValid() || serverURI == null || emailUTF8 == null || quickStretchedPW == null) {

View File

@ -23,6 +23,7 @@ import org.mozilla.gecko.fxa.authenticator.FxAccountLoginPolicy;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.crypto.KeyBundle;
import org.mozilla.gecko.sync.delegates.BaseGlobalSessionCallback;
import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
@ -157,26 +158,30 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
final CountDownLatch latch = new CountDownLatch(1);
final BaseGlobalSessionCallback callback = new SessionCallback(latch, syncResult);
try {
final String authEndpoint = FxAccountConstants.DEFAULT_AUTH_ENDPOINT;
final String tokenServerEndpoint = authEndpoint + (authEndpoint.endsWith("/") ? "" : "/") + "1.0/sync/1.1";
final URI tokenServerEndpointURI = new URI(tokenServerEndpoint);
final AndroidFxAccount fxAccount = new AndroidFxAccount(getContext(), account);
final Context context = getContext();
final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
fxAccount.dump();
}
final SharedPreferences sharedPrefs = getContext().getSharedPreferences(FxAccountConstants.PREFS_PATH, Context.MODE_PRIVATE); // TODO Ensure preferences are per-Account.
final String prefsPath = fxAccount.getSyncPrefsPath();
final FxAccountLoginPolicy loginPolicy = new FxAccountLoginPolicy(getContext(), fxAccount, executor);
// This will be the same chunk of SharedPreferences that GlobalSession/SyncConfiguration will later create.
final SharedPreferences sharedPrefs = context.getSharedPreferences(prefsPath, Utils.SHARED_PREFERENCES_MODE);
final String audience = fxAccount.getAudience();
final String tokenServerEndpoint = fxAccount.getTokenServerURI();
final URI tokenServerEndpointURI = new URI(tokenServerEndpoint);
// TODO: why doesn't the loginPolicy extract the audience from the account?
final FxAccountLoginPolicy loginPolicy = new FxAccountLoginPolicy(context, fxAccount, executor);
loginPolicy.certificateDurationInMilliseconds = 20 * 60 * 1000;
loginPolicy.assertionDurationInMilliseconds = 15 * 60 * 1000;
Logger.info(LOG_TAG, "Asking for certificates to expire after 20 minutes and assertions to expire after 15 minutes.");
loginPolicy.login(authEndpoint, new FxAccountLoginDelegate() {
loginPolicy.login(audience, new FxAccountLoginDelegate() {
@Override
public void handleSuccess(final String assertion) {
TokenServerClient tokenServerclient = new TokenServerClient(tokenServerEndpointURI, executor);
@ -201,7 +206,7 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
final long tokenServerSkew = tokenServerSkewHandler.getSkewInSeconds();
AuthHeaderProvider authHeaderProvider = new HawkAuthHeaderProvider(token.id, token.key.getBytes("UTF-8"), false, tokenServerSkew);
globalSession = new FxAccountGlobalSession(token.endpoint, token.uid, authHeaderProvider, FxAccountConstants.PREFS_PATH, syncKeyBundle, callback, getContext(), extras, clientsDataDelegate);
globalSession = new FxAccountGlobalSession(token.endpoint, token.uid, authHeaderProvider, prefsPath, syncKeyBundle, callback, context, extras, clientsDataDelegate);
globalSession.start();
} catch (Exception e) {
callback.handleError(globalSession, e);