/* 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.goanna.fxa.activities; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import org.mozilla.goanna.R; import org.mozilla.goanna.background.common.log.Logger; import org.mozilla.goanna.background.common.telemetry.TelemetryWrapper; import org.mozilla.goanna.background.fxa.FxAccountClient; import org.mozilla.goanna.background.fxa.FxAccountClient10.RequestDelegate; import org.mozilla.goanna.background.fxa.FxAccountClient20; import org.mozilla.goanna.background.fxa.FxAccountClient20.LoginResponse; import org.mozilla.goanna.background.fxa.FxAccountClientException.FxAccountClientRemoteException; import org.mozilla.goanna.background.fxa.FxAccountUtils; import org.mozilla.goanna.background.fxa.PasswordStretcher; import org.mozilla.goanna.fxa.FirefoxAccounts; import org.mozilla.goanna.fxa.authenticator.AndroidFxAccount; import org.mozilla.goanna.fxa.login.Engaged; import org.mozilla.goanna.fxa.login.State; import org.mozilla.goanna.fxa.tasks.FxAccountSignInTask; import org.mozilla.goanna.sync.setup.activities.ActivityUtils; import org.mozilla.goanna.sync.telemetry.TelemetryContract; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.AutoCompleteTextView; import android.widget.Button; import android.widget.EditText; import android.widget.ProgressBar; import android.widget.TextView; /** * Abstract activity which displays a screen for updating the local password. */ public abstract class FxAccountAbstractUpdateCredentialsActivity extends FxAccountAbstractSetupActivity { protected static final String LOG_TAG = FxAccountAbstractUpdateCredentialsActivity.class.getSimpleName(); protected AndroidFxAccount fxAccount; protected final int layoutResourceId; public FxAccountAbstractUpdateCredentialsActivity(int layoutResourceId) { // We want to share code with the other setup activities, but this activity // doesn't create a new Android Account, it modifies an existing one. If you // manage to get an account, and somehow be locked out too, we'll let you // update it. super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST); this.layoutResourceId = layoutResourceId; } /** * {@inheritDoc} */ @Override public void onCreate(Bundle icicle) { Logger.debug(LOG_TAG, "onCreate(" + icicle + ")"); super.onCreate(icicle); setContentView(layoutResourceId); emailEdit = (AutoCompleteTextView) ensureFindViewById(null, R.id.email, "email edit"); passwordEdit = (EditText) ensureFindViewById(null, R.id.password, "password edit"); showPasswordButton = (Button) ensureFindViewById(null, R.id.show_password, "show password button"); remoteErrorTextView = (TextView) ensureFindViewById(null, R.id.remote_error, "remote error text view"); button = (Button) ensureFindViewById(null, R.id.button, "update credentials"); progressBar = (ProgressBar) ensureFindViewById(null, R.id.progress, "progress bar"); minimumPasswordLength = 1; // Minimal restriction on passwords entered to sign in. createButton(); addListeners(); updateButtonState(); createShowPasswordButton(); emailEdit.setEnabled(false); TextView view = (TextView) findViewById(R.id.forgot_password_link); ActivityUtils.linkTextView(view, R.string.fxaccount_sign_in_forgot_password, R.string.fxaccount_link_forgot_password); updateFromIntentExtras(); maybeEnableAnimations(); } protected class UpdateCredentialsDelegate implements RequestDelegate { public final String email; public final String serverURI; public final PasswordStretcher passwordStretcher; public UpdateCredentialsDelegate(String email, PasswordStretcher passwordStretcher, String serverURI) { this.email = email; this.serverURI = serverURI; this.passwordStretcher = passwordStretcher; } @Override public void handleError(Exception e) { showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error); } @Override public void handleFailure(FxAccountClientRemoteException e) { if (e.isUpgradeRequired()) { Logger.error(LOG_TAG, "Got upgrade required from remote server; transitioning Firefox Account to Doghouse state."); final State state = fxAccount.getState(); fxAccount.setState(state.makeDoghouseState()); // The status activity will say that the user needs to upgrade. redirectToActivity(FxAccountStatusActivity.class); return; } showRemoteError(e, R.string.fxaccount_update_credentials_unknown_error); } @Override public void handleSuccess(LoginResponse result) { Logger.info(LOG_TAG, "Got success signing in."); if (fxAccount == null) { this.handleError(new IllegalStateException("fxAccount must not be null")); return; } 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); return; } fxAccount.setState(new Engaged(email, result.uid, result.verified, unwrapkB, result.sessionToken, result.keyFetchToken)); fxAccount.requestSync(FirefoxAccounts.FORCE); // For great debugging. if (FxAccountUtils.LOG_PERSONAL_INFORMATION) { fxAccount.dump(); } setResult(RESULT_OK); // Maybe show success activity. final Intent successIntent = makeSuccessIntent(email, result); if (successIntent != null) { startActivity(successIntent); } finish(); TelemetryWrapper.addToHistogram(TelemetryContract.SYNC11_MIGRATIONS_COMPLETED, 1); } } public void updateCredentials(String email, String password) { String serverURI = fxAccount.getAccountServerURI(); Executor executor = Executors.newSingleThreadExecutor(); FxAccountClient client = new FxAccountClient20(serverURI, executor); PasswordStretcher passwordStretcher = makePasswordStretcher(password); try { hideRemoteError(); RequestDelegate delegate = new UpdateCredentialsDelegate(email, passwordStretcher, serverURI); new FxAccountSignInTask(this, this, email, passwordStretcher, client, getQueryParameters(), 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); } } protected void createButton() { button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { final String email = emailEdit.getText().toString(); final String password = passwordEdit.getText().toString(); updateCredentials(email, password); } }); } }