mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 963160 - Use server provided alternate email address. r=rnewman
This commit is contained in:
parent
31d5dff6af
commit
600423b2a2
@ -510,6 +510,8 @@ sync_java_files = [
|
||||
'background/fxa/FxAccountClientException.java',
|
||||
'background/fxa/FxAccountRemoteError.java',
|
||||
'background/fxa/FxAccountUtils.java',
|
||||
'background/fxa/PasswordStretcher.java',
|
||||
'background/fxa/QuickPasswordStretcher.java',
|
||||
'background/fxa/SkewHandler.java',
|
||||
'background/healthreport/Environment.java',
|
||||
'background/healthreport/EnvironmentBuilder.java',
|
||||
@ -568,7 +570,6 @@ sync_java_files = [
|
||||
'fxa/login/FxAccountLoginStateMachine.java',
|
||||
'fxa/login/FxAccountLoginTransition.java',
|
||||
'fxa/login/Married.java',
|
||||
'fxa/login/Promised.java',
|
||||
'fxa/login/Separated.java',
|
||||
'fxa/login/State.java',
|
||||
'fxa/login/StateFactory.java',
|
||||
|
@ -11,8 +11,10 @@ import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
|
||||
public interface FxAccountClient {
|
||||
public void loginAndGetKeys(final byte[] emailUTF8, final byte[] quickStretchedPW, final RequestDelegate<LoginResponse> requestDelegate);
|
||||
public void createAccountAndGetKeys(final byte[] emailUTF8, final PasswordStretcher passwordStretcher, final RequestDelegate<LoginResponse> delegate);
|
||||
public void loginAndGetKeys(final byte[] emailUTF8, final PasswordStretcher passwordStretcher, final RequestDelegate<LoginResponse> requestDelegate);
|
||||
public void status(byte[] sessionToken, RequestDelegate<StatusResponse> requestDelegate);
|
||||
public void keys(byte[] keyFetchToken, RequestDelegate<TwoKeys> requestDelegate);
|
||||
public void sign(byte[] sessionToken, ExtendedJSONObject publicKey, long certificateDurationInMilliseconds, RequestDelegate<String> requestDelegate);
|
||||
public void resendCode(byte[] sessionToken, RequestDelegate<Void> delegate);
|
||||
}
|
||||
|
@ -70,7 +70,14 @@ public class FxAccountClient10 {
|
||||
protected static final String[] requiredErrorStringFields = { JSON_KEY_ERROR, JSON_KEY_MESSAGE, JSON_KEY_INFO };
|
||||
protected static final String[] requiredErrorLongFields = { JSON_KEY_CODE, JSON_KEY_ERRNO };
|
||||
|
||||
/**
|
||||
* The server's URI.
|
||||
* <p>
|
||||
* We assume throughout that this ends with a trailing slash (and guarantee as
|
||||
* much in the constructor).
|
||||
*/
|
||||
protected final String serverURI;
|
||||
|
||||
protected final Executor executor;
|
||||
|
||||
public FxAccountClient10(String serverURI, Executor executor) {
|
||||
@ -81,6 +88,9 @@ public class FxAccountClient10 {
|
||||
throw new IllegalArgumentException("Must provide a non-null executor.");
|
||||
}
|
||||
this.serverURI = (serverURI.endsWith("/") ? serverURI : serverURI + "/") + VERSION_FRAGMENT;
|
||||
if (!this.serverURI.endsWith("/")) {
|
||||
throw new IllegalArgumentException("Constructed serverURI must end with a trailing slash: " + this.serverURI);
|
||||
}
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
@ -282,8 +292,9 @@ public class FxAccountClient10 {
|
||||
String error;
|
||||
String message;
|
||||
String info;
|
||||
ExtendedJSONObject body;
|
||||
try {
|
||||
ExtendedJSONObject body = new SyncStorageResponse(response).jsonObjectBody();
|
||||
body = new SyncStorageResponse(response).jsonObjectBody();
|
||||
body.throwIfFieldsMissingOrMisTyped(requiredErrorStringFields, String.class);
|
||||
body.throwIfFieldsMissingOrMisTyped(requiredErrorLongFields, Long.class);
|
||||
code = body.getLong(JSON_KEY_CODE).intValue();
|
||||
@ -294,7 +305,7 @@ public class FxAccountClient10 {
|
||||
} catch (Exception e) {
|
||||
throw new FxAccountClientMalformedResponseException(response);
|
||||
}
|
||||
throw new FxAccountClientRemoteException(response, code, errno, error, message, info);
|
||||
throw new FxAccountClientRemoteException(response, code, errno, error, message, info, body);
|
||||
}
|
||||
|
||||
public void createAccount(final String email, final byte[] stretchedPWBytes,
|
||||
|
@ -8,6 +8,9 @@ import java.net.URI;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.json.simple.JSONObject;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.sync.net.BaseResource;
|
||||
@ -23,31 +26,22 @@ public class FxAccountClient20 extends FxAccountClient10 implements FxAccountCli
|
||||
super(serverURI, executor);
|
||||
}
|
||||
|
||||
public void createAccount(final byte[] emailUTF8, final byte[] quickStretchedPW, final boolean preVerified,
|
||||
final RequestDelegate<String> delegate) {
|
||||
try {
|
||||
createAccount(new FxAccount20CreateDelegate(emailUTF8, quickStretchedPW, preVerified), delegate);
|
||||
} catch (final Exception e) {
|
||||
invokeHandleError(delegate, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thin container for login response.
|
||||
* <p>
|
||||
* The <code>remoteEmail</code> field is the email address as normalized by the
|
||||
* server, and is <b>not necessarily</b> the email address delivered to the
|
||||
* <code>login</code> or <code>create</code> call.
|
||||
*/
|
||||
public static class LoginResponse {
|
||||
public final String serverURI;
|
||||
public final String remoteEmail;
|
||||
public final String uid;
|
||||
public final byte[] sessionToken;
|
||||
public final boolean verified;
|
||||
public final byte[] keyFetchToken;
|
||||
|
||||
public LoginResponse(String serverURI, String uid, boolean verified, byte[] sessionToken, byte[] keyFetchToken) {
|
||||
// This is pretty awful.
|
||||
this.serverURI = serverURI.endsWith(VERSION_FRAGMENT) ?
|
||||
serverURI.substring(0, serverURI.length() - VERSION_FRAGMENT.length()) :
|
||||
serverURI;
|
||||
public LoginResponse(String remoteEmail, String uid, boolean verified, byte[] sessionToken, byte[] keyFetchToken) {
|
||||
this.remoteEmail = remoteEmail;
|
||||
this.uid = uid;
|
||||
this.verified = verified;
|
||||
this.sessionToken = sessionToken;
|
||||
@ -55,16 +49,6 @@ public class FxAccountClient20 extends FxAccountClient10 implements FxAccountCli
|
||||
}
|
||||
}
|
||||
|
||||
public void login(final byte[] emailUTF8, final byte[] quickStretchedPW,
|
||||
final RequestDelegate<LoginResponse> delegate) {
|
||||
login(emailUTF8, quickStretchedPW, false, delegate);
|
||||
}
|
||||
|
||||
public void loginAndGetKeys(final byte[] emailUTF8, final byte[] quickStretchedPW,
|
||||
final RequestDelegate<LoginResponse> delegate) {
|
||||
login(emailUTF8, quickStretchedPW, true, delegate);
|
||||
}
|
||||
|
||||
// Public for testing only; prefer login and loginAndGetKeys (without boolean parameter).
|
||||
public void login(final byte[] emailUTF8, final byte[] quickStretchedPW, final boolean getKeys,
|
||||
final RequestDelegate<LoginResponse> delegate) {
|
||||
@ -83,18 +67,12 @@ public class FxAccountClient20 extends FxAccountClient10 implements FxAccountCli
|
||||
@Override
|
||||
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
|
||||
try {
|
||||
String[] requiredStringFields;
|
||||
if (!getKeys) {
|
||||
requiredStringFields = LOGIN_RESPONSE_REQUIRED_STRING_FIELDS;
|
||||
} else {
|
||||
requiredStringFields = LOGIN_RESPONSE_REQUIRED_STRING_FIELDS_KEYS;
|
||||
}
|
||||
String[] requiredBooleanFields = LOGIN_RESPONSE_REQUIRED_BOOLEAN_FIELDS;
|
||||
|
||||
final String[] requiredStringFields = getKeys ? LOGIN_RESPONSE_REQUIRED_STRING_FIELDS_KEYS : LOGIN_RESPONSE_REQUIRED_STRING_FIELDS;
|
||||
body.throwIfFieldsMissingOrMisTyped(requiredStringFields, String.class);
|
||||
|
||||
final String[] requiredBooleanFields = LOGIN_RESPONSE_REQUIRED_BOOLEAN_FIELDS;
|
||||
body.throwIfFieldsMissingOrMisTyped(requiredBooleanFields, Boolean.class);
|
||||
|
||||
LoginResponse loginResponse;
|
||||
String uid = body.getString(JSON_KEY_UID);
|
||||
boolean verified = body.getBoolean(JSON_KEY_VERIFIED);
|
||||
byte[] sessionToken = Utils.hex2Byte(body.getString(JSON_KEY_SESSIONTOKEN));
|
||||
@ -102,7 +80,7 @@ public class FxAccountClient20 extends FxAccountClient10 implements FxAccountCli
|
||||
if (getKeys) {
|
||||
keyFetchToken = Utils.hex2Byte(body.getString(JSON_KEY_KEYFETCHTOKEN));
|
||||
}
|
||||
loginResponse = new LoginResponse(serverURI, uid, verified, sessionToken, keyFetchToken);
|
||||
LoginResponse loginResponse = new LoginResponse(new String(emailUTF8, "UTF-8"), uid, verified, sessionToken, keyFetchToken);
|
||||
|
||||
delegate.handleSuccess(loginResponse);
|
||||
return;
|
||||
@ -115,4 +93,137 @@ public class FxAccountClient20 extends FxAccountClient10 implements FxAccountCli
|
||||
|
||||
post(resource, body, delegate);
|
||||
}
|
||||
|
||||
public void createAccount(final byte[] emailUTF8, final byte[] quickStretchedPW, final boolean getKeys, final boolean preVerified,
|
||||
final RequestDelegate<LoginResponse> delegate) {
|
||||
BaseResource resource;
|
||||
JSONObject body;
|
||||
final String path = getKeys ? "account/create?keys=true" : "account/create";
|
||||
try {
|
||||
resource = new BaseResource(new URI(serverURI + path));
|
||||
body = new FxAccount20CreateDelegate(emailUTF8, quickStretchedPW, preVerified).getCreateBody();
|
||||
} catch (Exception e) {
|
||||
invokeHandleError(delegate, e);
|
||||
return;
|
||||
}
|
||||
|
||||
// This is very similar to login, except verified is not required.
|
||||
resource.delegate = new ResourceDelegate<LoginResponse>(resource, delegate) {
|
||||
@Override
|
||||
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
|
||||
try {
|
||||
final String[] requiredStringFields = getKeys ? LOGIN_RESPONSE_REQUIRED_STRING_FIELDS_KEYS : LOGIN_RESPONSE_REQUIRED_STRING_FIELDS;
|
||||
body.throwIfFieldsMissingOrMisTyped(requiredStringFields, String.class);
|
||||
|
||||
String uid = body.getString(JSON_KEY_UID);
|
||||
boolean verified = false; // In production, we're definitely not verified immediately upon creation.
|
||||
Boolean tempVerified = body.getBoolean(JSON_KEY_VERIFIED);
|
||||
if (tempVerified != null) {
|
||||
verified = tempVerified.booleanValue();
|
||||
}
|
||||
byte[] sessionToken = Utils.hex2Byte(body.getString(JSON_KEY_SESSIONTOKEN));
|
||||
byte[] keyFetchToken = null;
|
||||
if (getKeys) {
|
||||
keyFetchToken = Utils.hex2Byte(body.getString(JSON_KEY_KEYFETCHTOKEN));
|
||||
}
|
||||
LoginResponse loginResponse = new LoginResponse(new String(emailUTF8, "UTF-8"), uid, verified, sessionToken, keyFetchToken);
|
||||
|
||||
delegate.handleSuccess(loginResponse);
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
delegate.handleError(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
post(resource, body, delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createAccountAndGetKeys(byte[] emailUTF8, PasswordStretcher passwordStretcher, RequestDelegate<LoginResponse> delegate) {
|
||||
try {
|
||||
byte[] quickStretchedPW = passwordStretcher.getQuickStretchedPW(emailUTF8);
|
||||
createAccount(emailUTF8, quickStretchedPW, true, false, delegate);
|
||||
} catch (Exception e) {
|
||||
invokeHandleError(delegate, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loginAndGetKeys(byte[] emailUTF8, PasswordStretcher passwordStretcher, RequestDelegate<LoginResponse> delegate) {
|
||||
login(emailUTF8, passwordStretcher, true, delegate);
|
||||
}
|
||||
|
||||
/**
|
||||
* We want users to be able to enter their email address case-insensitively.
|
||||
* We stretch the password locally using the email address as a salt, to make
|
||||
* dictionary attacks more expensive. This means that a client with a
|
||||
* case-differing email address is unable to produce the correct
|
||||
* authorization, even though it knows the password. In this case, the server
|
||||
* returns the email that the account was created with, so that the client can
|
||||
* re-stretch the password locally with the correct email salt. This version
|
||||
* of <code>login</code> retries at most one time with a server provided email
|
||||
* address.
|
||||
* <p>
|
||||
* Be aware that consumers will not see the initial error response from the
|
||||
* server providing an alternate email (if there is one).
|
||||
*
|
||||
* @param emailUTF8
|
||||
* user entered email address.
|
||||
* @param stretcher
|
||||
* delegate to stretch and re-stretch password.
|
||||
* @param getKeys
|
||||
* true if a <code>keyFetchToken</code> should be returned (in
|
||||
* addition to the standard <code>sessionToken</code>).
|
||||
* @param delegate
|
||||
* to invoke callbacks.
|
||||
*/
|
||||
public void login(final byte[] emailUTF8, final PasswordStretcher stretcher, final boolean getKeys,
|
||||
final RequestDelegate<LoginResponse> delegate) {
|
||||
byte[] quickStretchedPW;
|
||||
try {
|
||||
FxAccountConstants.pii(LOG_TAG, "Trying user provided email: '" + new String(emailUTF8, "UTF-8") + "'" );
|
||||
quickStretchedPW = stretcher.getQuickStretchedPW(emailUTF8);
|
||||
} catch (Exception e) {
|
||||
delegate.handleError(e);
|
||||
return;
|
||||
}
|
||||
|
||||
this.login(emailUTF8, quickStretchedPW, getKeys, new RequestDelegate<LoginResponse>() {
|
||||
@Override
|
||||
public void handleSuccess(LoginResponse result) {
|
||||
delegate.handleSuccess(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(Exception e) {
|
||||
delegate.handleError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFailure(FxAccountClientRemoteException e) {
|
||||
String alternateEmail = e.body.getString(JSON_KEY_EMAIL);
|
||||
if (!e.isBadEmailCase() || alternateEmail == null) {
|
||||
delegate.handleFailure(e);
|
||||
return;
|
||||
};
|
||||
|
||||
Logger.info(LOG_TAG, "Server returned alternate email; retrying login with provided email.");
|
||||
FxAccountConstants.pii(LOG_TAG, "Trying server provided email: '" + alternateEmail + "'" );
|
||||
|
||||
try {
|
||||
// Nota bene: this is not recursive, since we call the fixed password
|
||||
// signature here, which invokes a non-retrying version.
|
||||
byte[] alternateEmailUTF8 = alternateEmail.getBytes("UTF-8");
|
||||
byte[] alternateQuickStretchedPW = stretcher.getQuickStretchedPW(alternateEmailUTF8);
|
||||
login(alternateEmailUTF8, alternateQuickStretchedPW, getKeys, delegate);
|
||||
} catch (Exception innerException) {
|
||||
delegate.handleError(innerException);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
package org.mozilla.gecko.background.fxa;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.HTTPFailureException;
|
||||
import org.mozilla.gecko.sync.net.SyncStorageResponse;
|
||||
|
||||
@ -34,15 +35,20 @@ public class FxAccountClientException extends Exception {
|
||||
public final String error;
|
||||
public final String message;
|
||||
public final String info;
|
||||
public final ExtendedJSONObject body;
|
||||
|
||||
public FxAccountClientRemoteException(HttpResponse response, long httpStatusCode, long apiErrorNumber, String error, String message, String info) {
|
||||
public FxAccountClientRemoteException(HttpResponse response, long httpStatusCode, long apiErrorNumber, String error, String message, String info, ExtendedJSONObject body) {
|
||||
super(new HTTPFailureException(new SyncStorageResponse(response)));
|
||||
if (body == null) {
|
||||
throw new IllegalArgumentException("body must not be null");
|
||||
}
|
||||
this.response = response;
|
||||
this.httpStatusCode = httpStatusCode;
|
||||
this.apiErrorNumber = apiErrorNumber;
|
||||
this.error = error;
|
||||
this.message = message;
|
||||
this.info = info;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -55,6 +61,10 @@ public class FxAccountClientException extends Exception {
|
||||
}
|
||||
|
||||
public boolean isAccountAlreadyExists() {
|
||||
return apiErrorNumber == FxAccountRemoteError.ATTEMPT_TO_CREATE_AN_ACCOUNT_THAT_ALREADY_EXISTS;
|
||||
}
|
||||
|
||||
public boolean isAccountDoesNotExist() {
|
||||
return apiErrorNumber == FxAccountRemoteError.ATTEMPT_TO_ACCESS_AN_ACCOUNT_THAT_DOES_NOT_EXIST;
|
||||
}
|
||||
|
||||
@ -74,35 +84,44 @@ public class FxAccountClientException extends Exception {
|
||||
apiErrorNumber == FxAccountRemoteError.INCORRECT_API_VERSION_FOR_THIS_ACCOUNT;
|
||||
}
|
||||
|
||||
public boolean isTooManyRequests() {
|
||||
return apiErrorNumber == FxAccountRemoteError.CLIENT_HAS_SENT_TOO_MANY_REQUESTS;
|
||||
}
|
||||
|
||||
public boolean isServerUnavailable() {
|
||||
return apiErrorNumber == FxAccountRemoteError.SERVICE_TEMPORARILY_UNAVAILABLE_DUE_TO_HIGH_LOAD;
|
||||
}
|
||||
|
||||
public boolean isBadEmailCase() {
|
||||
return apiErrorNumber == FxAccountRemoteError.INCORRECT_EMAIL_CASE;
|
||||
}
|
||||
|
||||
public int getErrorMessageStringResource() {
|
||||
if (isUpgradeRequired()) {
|
||||
return R.string.fxaccount_remote_error_UPGRADE_REQUIRED;
|
||||
}
|
||||
switch ((int) apiErrorNumber) {
|
||||
case FxAccountRemoteError.ATTEMPT_TO_CREATE_AN_ACCOUNT_THAT_ALREADY_EXISTS:
|
||||
} else if (isAccountAlreadyExists()) {
|
||||
return R.string.fxaccount_remote_error_ATTEMPT_TO_CREATE_AN_ACCOUNT_THAT_ALREADY_EXISTS;
|
||||
case FxAccountRemoteError.ATTEMPT_TO_ACCESS_AN_ACCOUNT_THAT_DOES_NOT_EXIST:
|
||||
} else if (isAccountDoesNotExist()) {
|
||||
return R.string.fxaccount_remote_error_ATTEMPT_TO_ACCESS_AN_ACCOUNT_THAT_DOES_NOT_EXIST;
|
||||
case FxAccountRemoteError.INCORRECT_PASSWORD:
|
||||
} else if (isBadPassword()) {
|
||||
return R.string.fxaccount_remote_error_INCORRECT_PASSWORD;
|
||||
case FxAccountRemoteError.ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT:
|
||||
} else if (isUnverified()) {
|
||||
return R.string.fxaccount_remote_error_ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT;
|
||||
case FxAccountRemoteError.CLIENT_HAS_SENT_TOO_MANY_REQUESTS:
|
||||
} else if (isTooManyRequests()) {
|
||||
return R.string.fxaccount_remote_error_CLIENT_HAS_SENT_TOO_MANY_REQUESTS;
|
||||
case FxAccountRemoteError.SERVICE_TEMPORARILY_UNAVAILABLE_DUE_TO_HIGH_LOAD:
|
||||
} else if (isServerUnavailable()) {
|
||||
return R.string.fxaccount_remote_error_SERVICE_TEMPORARILY_UNAVAILABLE_TO_DUE_HIGH_LOAD;
|
||||
default:
|
||||
} else {
|
||||
return R.string.fxaccount_remote_error_UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class FxAccountClientMalformedResponseException extends FxAccountClientRemoteException {
|
||||
private static final long serialVersionUID = 2209313149952001098L;
|
||||
|
||||
public FxAccountClientMalformedResponseException(HttpResponse response) {
|
||||
super(response, 0, FxAccountRemoteError.UNKNOWN_ERROR, "Response malformed", "Response malformed", "Response malformed");
|
||||
super(response, 0, FxAccountRemoteError.UNKNOWN_ERROR, "Response malformed", "Response malformed", "Response malformed", new ExtendedJSONObject());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ public interface FxAccountRemoteError {
|
||||
public static final int INCORRECT_LOGIN_METHOD_FOR_THIS_ACCOUNT = 117;
|
||||
public static final int INCORRECT_KEY_RETRIEVAL_METHOD_FOR_THIS_ACCOUNT = 118;
|
||||
public static final int INCORRECT_API_VERSION_FOR_THIS_ACCOUNT = 119;
|
||||
public static final int INCORRECT_EMAIL_CASE = 120;
|
||||
public static final int SERVICE_TEMPORARILY_UNAVAILABLE_DUE_TO_HIGH_LOAD = 201;
|
||||
public static final int UNKNOWN_ERROR = 999;
|
||||
}
|
||||
|
12
mobile/android/base/background/fxa/PasswordStretcher.java
Normal file
12
mobile/android/base/background/fxa/PasswordStretcher.java
Normal file
@ -0,0 +1,12 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.background.fxa;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
public interface PasswordStretcher {
|
||||
public byte[] getQuickStretchedPW(byte[] emailUTF8) throws UnsupportedEncodingException, GeneralSecurityException;
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.background.fxa;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
|
||||
public class QuickPasswordStretcher implements PasswordStretcher {
|
||||
protected final String password;
|
||||
protected final Map<String, String> cache = new HashMap<String, String>();
|
||||
|
||||
public QuickPasswordStretcher(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public synchronized byte[] getQuickStretchedPW(byte[] emailUTF8) throws UnsupportedEncodingException, GeneralSecurityException {
|
||||
if (emailUTF8 == null) {
|
||||
throw new IllegalArgumentException("emailUTF8 must not be null");
|
||||
}
|
||||
String key = Utils.byte2Hex(emailUTF8);
|
||||
if (!cache.containsKey(key)) {
|
||||
byte[] value = FxAccountUtils.generateQuickStretchedPW(emailUTF8, password.getBytes("UTF-8"));
|
||||
cache.put(key, Utils.byte2Hex(value));
|
||||
return value;
|
||||
}
|
||||
return Utils.hex2Byte(cache.get(key));
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ public class FxAccountConstants {
|
||||
public static final String GLOBAL_LOG_TAG = "FxAccounts";
|
||||
public static final String ACCOUNT_TYPE = "@MOZ_ANDROID_SHARED_FXACCOUNT_TYPE@";
|
||||
|
||||
public static final String DEFAULT_IDP_ENDPOINT = "https://api-accounts.dev.lcip.org";
|
||||
public static final String DEFAULT_IDP_ENDPOINT = "https://api-accounts-latest.dev.lcip.org";
|
||||
public static final String DEFAULT_TOKEN_SERVER_ENDPOINT = "https://token.dev.lcip.org";
|
||||
public static final String DEFAULT_TOKEN_SERVER_URI = DEFAULT_TOKEN_SERVER_ENDPOINT +
|
||||
(DEFAULT_TOKEN_SERVER_ENDPOINT.endsWith("/") ? "" : "/") + "1.0/sync/1.5";
|
||||
|
@ -8,9 +8,20 @@ import java.io.IOException;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.ProgressDisplay;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.Engaged;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.sync.setup.Constants;
|
||||
|
||||
import android.accounts.AccountManager;
|
||||
import android.content.Intent;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextWatcher;
|
||||
@ -164,4 +175,83 @@ abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractAc
|
||||
progressBar.setVisibility(View.INVISIBLE);
|
||||
button.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public Intent makeSuccessIntent(String email, LoginResponse result) {
|
||||
Intent successIntent;
|
||||
if (result.verified) {
|
||||
successIntent = new Intent(this, FxAccountVerifiedAccountActivity.class);
|
||||
} else {
|
||||
successIntent = new Intent(this, FxAccountConfirmAccountActivity.class);
|
||||
successIntent.putExtra("sessionToken", result.sessionToken);
|
||||
}
|
||||
successIntent.putExtra("email", email);
|
||||
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
|
||||
// the soft keyboard not being shown for the started activity. Why, Android, why?
|
||||
successIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||
return successIntent;
|
||||
}
|
||||
|
||||
protected abstract class AddAccountDelegate implements RequestDelegate<LoginResponse> {
|
||||
public final String email;
|
||||
public final PasswordStretcher passwordStretcher;
|
||||
public final String serverURI;
|
||||
|
||||
public AddAccountDelegate(String email, PasswordStretcher passwordStretcher, String serverURI) {
|
||||
this.email = email;
|
||||
this.passwordStretcher = passwordStretcher;
|
||||
this.serverURI = serverURI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSuccess(LoginResponse result) {
|
||||
Logger.info(LOG_TAG, "Got success response; adding Android account.");
|
||||
|
||||
// We're on the UI thread, but it's okay to create the account here.
|
||||
AndroidFxAccount fxAccount;
|
||||
try {
|
||||
final String profile = Constants.DEFAULT_PROFILE;
|
||||
final String tokenServerURI = FxAccountConstants.DEFAULT_TOKEN_SERVER_URI;
|
||||
// It is crucial that we use the email address provided by the server
|
||||
// (rather than whatever the user entered), because the user's keys are
|
||||
// wrapped and salted with the initial email they provided to
|
||||
// /create/account. Of course, we want to pass through what the user
|
||||
// entered locally as much as possible, so we create the Android account
|
||||
// with their entered email address, etc.
|
||||
// The passwordStretcher should have seen this email address before, so
|
||||
// we shouldn't be calculating the expensive stretch twice.
|
||||
byte[] quickStretchedPW = passwordStretcher.getQuickStretchedPW(result.remoteEmail.getBytes("UTF-8"));
|
||||
byte[] unwrapkB = FxAccountUtils.generateUnwrapBKey(quickStretchedPW);
|
||||
State state = new Engaged(email, result.uid, result.verified, unwrapkB, result.sessionToken, result.keyFetchToken);
|
||||
fxAccount = AndroidFxAccount.addAndroidAccount(getApplicationContext(),
|
||||
email,
|
||||
profile,
|
||||
serverURI,
|
||||
tokenServerURI,
|
||||
state);
|
||||
if (fxAccount == null) {
|
||||
throw new RuntimeException("Could not add Android account.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
handleError(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// For great debugging.
|
||||
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
||||
fxAccount.dump();
|
||||
}
|
||||
|
||||
// The GetStarted activity has called us and needs to return a result to the authenticator.
|
||||
final Intent intent = new Intent();
|
||||
intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, email);
|
||||
intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, FxAccountConstants.ACCOUNT_TYPE);
|
||||
// intent.putExtra(AccountManager.KEY_AUTHTOKEN, accountType);
|
||||
setResult(RESULT_OK, intent);
|
||||
|
||||
// Show success activity depending on verification status.
|
||||
Intent successIntent = makeSuccessIntent(email, result);
|
||||
startActivity(successIntent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import java.util.concurrent.Executors;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||
@ -85,7 +86,7 @@ public class FxAccountConfirmAccountActivity extends Activity implements OnClick
|
||||
|
||||
protected final byte[] sessionToken;
|
||||
|
||||
public FxAccountResendCodeTask(Context context, byte[] sessionToken, FxAccountClient20 client, RequestDelegate<Void> delegate) {
|
||||
public FxAccountResendCodeTask(Context context, byte[] sessionToken, FxAccountClient client, RequestDelegate<Void> delegate) {
|
||||
super(context, null, client, delegate);
|
||||
this.sessionToken = sessionToken;
|
||||
}
|
||||
@ -126,7 +127,7 @@ public class FxAccountConfirmAccountActivity extends Activity implements OnClick
|
||||
String serverURI = FxAccountConstants.DEFAULT_IDP_ENDPOINT;
|
||||
RequestDelegate<Void> delegate = new ResendCodeDelegate();
|
||||
Executor executor = Executors.newSingleThreadExecutor();
|
||||
FxAccountClient20 client = new FxAccountClient20(serverURI, executor);
|
||||
FxAccountClient client = new FxAccountClient20(serverURI, executor);
|
||||
new FxAccountResendCodeTask(this, sessionToken, client, delegate).execute();
|
||||
}
|
||||
|
||||
|
@ -10,19 +10,16 @@ import java.util.concurrent.Executors;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
||||
import org.mozilla.gecko.background.fxa.QuickPasswordStretcher;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountCreateAccountTask;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.Promised;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.sync.setup.Constants;
|
||||
|
||||
import android.accounts.AccountManager;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
@ -134,85 +131,28 @@ public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivi
|
||||
});
|
||||
}
|
||||
|
||||
protected class CreateAccountDelegate implements RequestDelegate<String> {
|
||||
public final String email;
|
||||
public final String password;
|
||||
public final String serverURI;
|
||||
|
||||
public CreateAccountDelegate(String email, String password, String serverURI) {
|
||||
this.email = email;
|
||||
this.password = password;
|
||||
this.serverURI = serverURI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(Exception e) {
|
||||
showRemoteError(e, R.string.fxaccount_create_account_unknown_error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFailure(final FxAccountClientRemoteException e) {
|
||||
showRemoteError(e, R.string.fxaccount_create_account_unknown_error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSuccess(String uid) {
|
||||
Activity activity = FxAccountCreateAccountActivity.this;
|
||||
Logger.info(LOG_TAG, "Got success creating account.");
|
||||
|
||||
// We're on the UI thread, but it's okay to create the account here.
|
||||
AndroidFxAccount fxAccount;
|
||||
try {
|
||||
final String profile = Constants.DEFAULT_PROFILE;
|
||||
final String tokenServerURI = FxAccountConstants.DEFAULT_TOKEN_SERVER_URI;
|
||||
// TODO: This is wasteful. We should be able to thread these through so they don't get recomputed.
|
||||
byte[] quickStretchedPW = FxAccountUtils.generateQuickStretchedPW(email.getBytes("UTF-8"), password.getBytes("UTF-8"));
|
||||
byte[] unwrapkB = FxAccountUtils.generateUnwrapBKey(quickStretchedPW);
|
||||
State state = new Promised(email, uid, false, unwrapkB, quickStretchedPW);
|
||||
fxAccount = AndroidFxAccount.addAndroidAccount(activity, email, password,
|
||||
profile,
|
||||
serverURI,
|
||||
tokenServerURI,
|
||||
state);
|
||||
if (fxAccount == null) {
|
||||
throw new RuntimeException("Could not add Android account.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
handleError(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// For great debugging.
|
||||
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
||||
fxAccount.dump();
|
||||
}
|
||||
|
||||
// The GetStarted activity has called us and needs to return a result to the authenticator.
|
||||
final Intent intent = new Intent();
|
||||
intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, email);
|
||||
intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, FxAccountConstants.ACCOUNT_TYPE);
|
||||
// intent.putExtra(AccountManager.KEY_AUTHTOKEN, accountType);
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
|
||||
// Show success activity.
|
||||
Intent successIntent = new Intent(FxAccountCreateAccountActivity.this, FxAccountConfirmAccountActivity.class);
|
||||
successIntent.putExtra("email", email);
|
||||
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
|
||||
// the soft keyboard not being shown for the started activity. Why, Android, why?
|
||||
successIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||
startActivity(successIntent);
|
||||
}
|
||||
}
|
||||
|
||||
public void createAccount(String email, String password) {
|
||||
String serverURI = FxAccountConstants.DEFAULT_IDP_ENDPOINT;
|
||||
RequestDelegate<String> delegate = new CreateAccountDelegate(email, password, serverURI);
|
||||
PasswordStretcher passwordStretcher = new QuickPasswordStretcher(password);
|
||||
// This delegate creates a new Android account on success, opens the
|
||||
// appropriate "success!" activity, and finishes this activity.
|
||||
RequestDelegate<LoginResponse> delegate = new AddAccountDelegate(email, passwordStretcher, serverURI) {
|
||||
@Override
|
||||
public void handleError(Exception e) {
|
||||
showRemoteError(e, R.string.fxaccount_create_account_unknown_error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFailure(FxAccountClientRemoteException e) {
|
||||
showRemoteError(e, R.string.fxaccount_create_account_unknown_error);
|
||||
}
|
||||
};
|
||||
|
||||
Executor executor = Executors.newSingleThreadExecutor();
|
||||
FxAccountClient20 client = new FxAccountClient20(serverURI, executor);
|
||||
FxAccountClient client = new FxAccountClient20(serverURI, executor);
|
||||
try {
|
||||
hideRemoteError();
|
||||
new FxAccountCreateAccountTask(this, this, email, password, client, delegate).execute();
|
||||
new FxAccountCreateAccountTask(this, this, email, passwordStretcher, client, delegate).execute();
|
||||
} catch (Exception e) {
|
||||
showRemoteError(e, R.string.fxaccount_create_account_unknown_error);
|
||||
}
|
||||
|
@ -5,15 +5,14 @@
|
||||
package org.mozilla.gecko.fxa.activities;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.InnerRequestDelegate;
|
||||
|
||||
import android.content.Context;
|
||||
@ -37,7 +36,7 @@ abstract class FxAccountSetupTask<T> extends AsyncTask<Void, Void, InnerRequestD
|
||||
}
|
||||
|
||||
protected final Context context;
|
||||
protected final FxAccountClient20 client;
|
||||
protected final FxAccountClient client;
|
||||
protected final ProgressDisplay progressDisplay;
|
||||
|
||||
// Initialized lazily.
|
||||
@ -49,7 +48,7 @@ abstract class FxAccountSetupTask<T> extends AsyncTask<Void, Void, InnerRequestD
|
||||
|
||||
protected final RequestDelegate<T> delegate;
|
||||
|
||||
public FxAccountSetupTask(Context context, ProgressDisplay progressDisplay, FxAccountClient20 client, RequestDelegate<T> delegate) {
|
||||
public FxAccountSetupTask(Context context, ProgressDisplay progressDisplay, FxAccountClient client, RequestDelegate<T> delegate) {
|
||||
this.context = context;
|
||||
this.client = client;
|
||||
this.delegate = delegate;
|
||||
@ -119,36 +118,22 @@ abstract class FxAccountSetupTask<T> extends AsyncTask<Void, Void, InnerRequestD
|
||||
}
|
||||
}
|
||||
|
||||
public static class FxAccountCreateAccountTask extends FxAccountSetupTask<String> {
|
||||
public static class FxAccountCreateAccountTask extends FxAccountSetupTask<LoginResponse> {
|
||||
private static final String LOG_TAG = FxAccountCreateAccountTask.class.getSimpleName();
|
||||
|
||||
protected final byte[] emailUTF8;
|
||||
protected final byte[] passwordUTF8;
|
||||
protected final PasswordStretcher passwordStretcher;
|
||||
|
||||
public FxAccountCreateAccountTask(Context context, ProgressDisplay progressDisplay, String email, String password, FxAccountClient20 client, RequestDelegate<String> delegate) throws UnsupportedEncodingException {
|
||||
public FxAccountCreateAccountTask(Context context, ProgressDisplay progressDisplay, String email, PasswordStretcher passwordStretcher, FxAccountClient client, RequestDelegate<LoginResponse> delegate) throws UnsupportedEncodingException {
|
||||
super(context, progressDisplay, client, delegate);
|
||||
this.emailUTF8 = email.getBytes("UTF-8");
|
||||
this.passwordUTF8 = password.getBytes("UTF-8");
|
||||
}
|
||||
|
||||
/**
|
||||
* Stretching the password is expensive, so we compute the stretched value lazily.
|
||||
*
|
||||
* @return stretched password.
|
||||
* @throws GeneralSecurityException
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
public byte[] generateQuickStretchedPW() throws UnsupportedEncodingException, GeneralSecurityException {
|
||||
if (this.quickStretchedPW == null) {
|
||||
this.quickStretchedPW = FxAccountUtils.generateQuickStretchedPW(emailUTF8, passwordUTF8);
|
||||
}
|
||||
return this.quickStretchedPW;
|
||||
this.passwordStretcher = passwordStretcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InnerRequestDelegate<String> doInBackground(Void... arg0) {
|
||||
protected InnerRequestDelegate<LoginResponse> doInBackground(Void... arg0) {
|
||||
try {
|
||||
client.createAccount(emailUTF8, generateQuickStretchedPW(), false, innerDelegate);
|
||||
client.createAccountAndGetKeys(emailUTF8, passwordStretcher, innerDelegate);
|
||||
latch.await();
|
||||
return innerDelegate;
|
||||
} catch (Exception e) {
|
||||
@ -163,32 +148,18 @@ abstract class FxAccountSetupTask<T> extends AsyncTask<Void, Void, InnerRequestD
|
||||
protected static final String LOG_TAG = FxAccountSignInTask.class.getSimpleName();
|
||||
|
||||
protected final byte[] emailUTF8;
|
||||
protected final byte[] passwordUTF8;
|
||||
protected final PasswordStretcher passwordStretcher;
|
||||
|
||||
public FxAccountSignInTask(Context context, ProgressDisplay progressDisplay, String email, String password, FxAccountClient20 client, RequestDelegate<LoginResponse> delegate) throws UnsupportedEncodingException {
|
||||
public FxAccountSignInTask(Context context, ProgressDisplay progressDisplay, String email, PasswordStretcher passwordStretcher, FxAccountClient client, RequestDelegate<LoginResponse> delegate) throws UnsupportedEncodingException {
|
||||
super(context, progressDisplay, client, delegate);
|
||||
this.emailUTF8 = email.getBytes("UTF-8");
|
||||
this.passwordUTF8 = password.getBytes("UTF-8");
|
||||
}
|
||||
|
||||
/**
|
||||
* Stretching the password is expensive, so we compute the stretched value lazily.
|
||||
*
|
||||
* @return stretched password.
|
||||
* @throws GeneralSecurityException
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
public byte[] generateQuickStretchedPW() throws UnsupportedEncodingException, GeneralSecurityException {
|
||||
if (this.quickStretchedPW == null) {
|
||||
this.quickStretchedPW = FxAccountUtils.generateQuickStretchedPW(emailUTF8, passwordUTF8);
|
||||
}
|
||||
return this.quickStretchedPW;
|
||||
this.passwordStretcher = passwordStretcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InnerRequestDelegate<LoginResponse> doInBackground(Void... arg0) {
|
||||
try {
|
||||
client.loginAndGetKeys(emailUTF8, generateQuickStretchedPW(), innerDelegate);
|
||||
client.loginAndGetKeys(emailUTF8, passwordStretcher, innerDelegate);
|
||||
latch.await();
|
||||
return innerDelegate;
|
||||
} catch (Exception e) {
|
||||
|
@ -9,20 +9,16 @@ import java.util.concurrent.Executors;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
||||
import org.mozilla.gecko.background.fxa.QuickPasswordStretcher;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountSignInTask;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.Engaged;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.sync.setup.Constants;
|
||||
|
||||
import android.accounts.AccountManager;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
@ -103,91 +99,28 @@ public class FxAccountSignInActivity extends FxAccountAbstractSetupActivity {
|
||||
this.finish();
|
||||
}
|
||||
|
||||
protected class SignInDelegate implements RequestDelegate<LoginResponse> {
|
||||
public final String email;
|
||||
public final String password;
|
||||
public final String serverURI;
|
||||
|
||||
public SignInDelegate(String email, String password, String serverURI) {
|
||||
this.email = email;
|
||||
this.password = password;
|
||||
this.serverURI = serverURI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(Exception e) {
|
||||
showRemoteError(e, R.string.fxaccount_sign_in_unknown_error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFailure(FxAccountClientRemoteException e) {
|
||||
showRemoteError(e, R.string.fxaccount_sign_in_unknown_error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSuccess(LoginResponse result) {
|
||||
Activity activity = FxAccountSignInActivity.this;
|
||||
Logger.info(LOG_TAG, "Got success signing in.");
|
||||
|
||||
// We're on the UI thread, but it's okay to create the account here.
|
||||
AndroidFxAccount fxAccount;
|
||||
try {
|
||||
final String profile = Constants.DEFAULT_PROFILE;
|
||||
final String tokenServerURI = FxAccountConstants.DEFAULT_TOKEN_SERVER_URI;
|
||||
// TODO: This is wasteful. We should be able to thread these through so they don't get recomputed.
|
||||
byte[] quickStretchedPW = FxAccountUtils.generateQuickStretchedPW(email.getBytes("UTF-8"), password.getBytes("UTF-8"));
|
||||
byte[] unwrapkB = FxAccountUtils.generateUnwrapBKey(quickStretchedPW);
|
||||
State state = new Engaged(email, result.uid, result.verified, unwrapkB, result.sessionToken, result.keyFetchToken);
|
||||
fxAccount = AndroidFxAccount.addAndroidAccount(activity, email, password,
|
||||
profile,
|
||||
serverURI,
|
||||
tokenServerURI,
|
||||
state);
|
||||
if (fxAccount == null) {
|
||||
throw new RuntimeException("Could not add Android account.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
handleError(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// For great debugging.
|
||||
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
||||
fxAccount.dump();
|
||||
}
|
||||
|
||||
// The GetStarted activity has called us and needs to return a result to the authenticator.
|
||||
final Intent intent = new Intent();
|
||||
intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, email);
|
||||
intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, FxAccountConstants.ACCOUNT_TYPE);
|
||||
// intent.putExtra(AccountManager.KEY_AUTHTOKEN, accountType);
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
|
||||
// Show success activity depending on verification status.
|
||||
Intent successIntent;
|
||||
if (result.verified) {
|
||||
successIntent = new Intent(FxAccountSignInActivity.this, FxAccountVerifiedAccountActivity.class);
|
||||
} else {
|
||||
successIntent = new Intent(FxAccountSignInActivity.this, FxAccountConfirmAccountActivity.class);
|
||||
successIntent.putExtra("sessionToken", result.sessionToken);
|
||||
}
|
||||
successIntent.putExtra("email", email);
|
||||
// Per http://stackoverflow.com/a/8992365, this triggers a known bug with
|
||||
// the soft keyboard not being shown for the started activity. Why, Android, why?
|
||||
successIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||
startActivity(successIntent);
|
||||
}
|
||||
}
|
||||
|
||||
public void signIn(String email, String password) {
|
||||
String serverURI = FxAccountConstants.DEFAULT_IDP_ENDPOINT;
|
||||
RequestDelegate<LoginResponse> delegate = new SignInDelegate(email, password, serverURI);
|
||||
PasswordStretcher passwordStretcher = new QuickPasswordStretcher(password);
|
||||
// This delegate creates a new Android account on success, opens the
|
||||
// appropriate "success!" activity, and finishes this activity.
|
||||
RequestDelegate<LoginResponse> delegate = new AddAccountDelegate(email, passwordStretcher, serverURI) {
|
||||
@Override
|
||||
public void handleError(Exception e) {
|
||||
showRemoteError(e, R.string.fxaccount_sign_in_unknown_error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFailure(FxAccountClientRemoteException e) {
|
||||
showRemoteError(e, R.string.fxaccount_sign_in_unknown_error);
|
||||
}
|
||||
};
|
||||
|
||||
Executor executor = Executors.newSingleThreadExecutor();
|
||||
FxAccountClient20 client = new FxAccountClient20(serverURI, executor);
|
||||
FxAccountClient client = new FxAccountClient20(serverURI, executor);
|
||||
try {
|
||||
hideRemoteError();
|
||||
new FxAccountSignInTask(this, this, email, password, client, delegate).execute();
|
||||
new FxAccountSignInTask(this, this, email, passwordStretcher, client, delegate).execute();
|
||||
} catch (Exception e) {
|
||||
showRemoteError(e, R.string.fxaccount_sign_in_unknown_error);
|
||||
}
|
||||
|
@ -4,18 +4,19 @@
|
||||
|
||||
package org.mozilla.gecko.fxa.activities;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
||||
import org.mozilla.gecko.background.fxa.QuickPasswordStretcher;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountSignInTask;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
@ -110,16 +111,13 @@ public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractSetupAc
|
||||
|
||||
protected class UpdateCredentialsDelegate implements RequestDelegate<LoginResponse> {
|
||||
public final String email;
|
||||
public final String password;
|
||||
public final String serverURI;
|
||||
public final byte[] quickStretchedPW;
|
||||
public final PasswordStretcher passwordStretcher;
|
||||
|
||||
public UpdateCredentialsDelegate(String email, String password, String serverURI) throws UnsupportedEncodingException, GeneralSecurityException {
|
||||
public UpdateCredentialsDelegate(String email, PasswordStretcher passwordStretcher, String serverURI) {
|
||||
this.email = email;
|
||||
this.password = password;
|
||||
this.serverURI = serverURI;
|
||||
// XXX This needs to be calculated lazily.
|
||||
this.quickStretchedPW = FxAccountUtils.generateQuickStretchedPW(email.getBytes("UTF-8"), password.getBytes("UTF-8"));
|
||||
this.passwordStretcher = passwordStretcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -144,6 +142,12 @@ public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractSetupAc
|
||||
|
||||
byte[] unwrapkB;
|
||||
try {
|
||||
// It is crucial that we use the email address provided by the server
|
||||
// (rather than whatever the user entered), because the user's keys are
|
||||
// wrapped and salted with the initial email they provided to
|
||||
// /create/account. Of course, we want to pass through what the user
|
||||
// entered locally as much as possible.
|
||||
byte[] quickStretchedPW = passwordStretcher.getQuickStretchedPW(result.remoteEmail.getBytes("UTF-8"));
|
||||
unwrapkB = FxAccountUtils.generateUnwrapBKey(quickStretchedPW);
|
||||
} catch (Exception e) {
|
||||
this.handleError(e);
|
||||
@ -163,11 +167,12 @@ public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractSetupAc
|
||||
public void updateCredentials(String email, String password) {
|
||||
String serverURI = FxAccountConstants.DEFAULT_IDP_ENDPOINT;
|
||||
Executor executor = Executors.newSingleThreadExecutor();
|
||||
FxAccountClient20 client = new FxAccountClient20(serverURI, executor);
|
||||
FxAccountClient client = new FxAccountClient20(serverURI, executor);
|
||||
PasswordStretcher passwordStretcher = new QuickPasswordStretcher(password);
|
||||
try {
|
||||
hideRemoteError();
|
||||
RequestDelegate<LoginResponse> delegate = new UpdateCredentialsDelegate(email, password, serverURI);
|
||||
new FxAccountSignInTask(this, this, email, password, client, delegate).execute();
|
||||
RequestDelegate<LoginResponse> delegate = new UpdateCredentialsDelegate(email, passwordStretcher, serverURI);
|
||||
new FxAccountSignInTask(this, this, email, passwordStretcher, client, delegate).execute();
|
||||
} catch (Exception e) {
|
||||
Logger.warn(LOG_TAG, "Got exception updating credentials for account.", e);
|
||||
showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error);
|
||||
|
@ -262,7 +262,6 @@ public class AndroidFxAccount {
|
||||
public static AndroidFxAccount addAndroidAccount(
|
||||
Context context,
|
||||
String email,
|
||||
String password,
|
||||
String profile,
|
||||
String idpServerURI,
|
||||
String tokenServerURI,
|
||||
@ -271,9 +270,6 @@ public class AndroidFxAccount {
|
||||
if (email == null) {
|
||||
throw new IllegalArgumentException("email must not be null");
|
||||
}
|
||||
if (password == null) {
|
||||
throw new IllegalArgumentException("password must not be null");
|
||||
}
|
||||
if (idpServerURI == null) {
|
||||
throw new IllegalArgumentException("idpServerURI must not be null");
|
||||
}
|
||||
@ -303,7 +299,10 @@ public class AndroidFxAccount {
|
||||
|
||||
Account account = new Account(email, FxAccountConstants.ACCOUNT_TYPE);
|
||||
AccountManager accountManager = AccountManager.get(context);
|
||||
boolean added = accountManager.addAccountExplicitly(account, null, userdata); // XXX what should the password be?
|
||||
// We don't set an Android password, because we don't want to persist the
|
||||
// password (or anything else as powerful as the password). Instead, we
|
||||
// internally manage a sessionToken with a remotely owned lifecycle.
|
||||
boolean added = accountManager.addAccountExplicitly(account, null, userdata);
|
||||
if (!added) {
|
||||
return null;
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ public abstract class State {
|
||||
public static final long CURRENT_VERSION = 1L;
|
||||
|
||||
public enum StateLabel {
|
||||
Promised,
|
||||
Engaged,
|
||||
Cohabiting,
|
||||
Married,
|
||||
|
@ -20,13 +20,6 @@ public class StateFactory {
|
||||
throw new IllegalStateException("version must be 1");
|
||||
}
|
||||
switch (stateLabel) {
|
||||
case Promised:
|
||||
return new Promised(
|
||||
o.getString("email"),
|
||||
o.getString("uid"),
|
||||
o.getBoolean("verified"),
|
||||
Utils.hex2Byte(o.getString("unwrapkB")),
|
||||
Utils.hex2Byte(o.getString("quickStretchedPW")));
|
||||
case Engaged:
|
||||
return new Engaged(
|
||||
o.getString("email"),
|
||||
|
Loading…
Reference in New Issue
Block a user