diff options
Diffstat (limited to 'mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/FxAccountAuthenticator.java')
-rw-r--r-- | mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/FxAccountAuthenticator.java | 385 |
1 files changed, 0 insertions, 385 deletions
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/FxAccountAuthenticator.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/FxAccountAuthenticator.java deleted file mode 100644 index 259b1cb88..000000000 --- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/FxAccountAuthenticator.java +++ /dev/null @@ -1,385 +0,0 @@ -/* 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.fxa.authenticator; - -import android.accounts.AbstractAccountAuthenticator; -import android.accounts.Account; -import android.accounts.AccountAuthenticatorResponse; -import android.accounts.AccountManager; -import android.accounts.NetworkErrorException; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import org.mozilla.gecko.background.common.log.Logger; -import org.mozilla.gecko.background.fxa.FxAccountClient; -import org.mozilla.gecko.background.fxa.FxAccountClient20; -import org.mozilla.gecko.background.fxa.FxAccountUtils; -import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClient.RequestDelegate; -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.AuthorizationResponse; -import org.mozilla.gecko.browserid.BrowserIDKeyPair; -import org.mozilla.gecko.browserid.JSONWebTokenUtils; -import org.mozilla.gecko.fxa.FxAccountConstants; -import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine; -import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.LoginStateMachineDelegate; -import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.Transition; -import org.mozilla.gecko.fxa.login.Married; -import org.mozilla.gecko.fxa.login.State; -import org.mozilla.gecko.fxa.login.State.StateLabel; -import org.mozilla.gecko.fxa.login.StateFactory; -import org.mozilla.gecko.fxa.receivers.FxAccountDeletedService; -import org.mozilla.gecko.fxa.sync.FxAccountNotificationManager; -import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter; -import org.mozilla.gecko.util.ThreadUtils; - -import java.security.NoSuchAlgorithmException; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - -public class FxAccountAuthenticator extends AbstractAccountAuthenticator { - public static final String LOG_TAG = FxAccountAuthenticator.class.getSimpleName(); - public static final int UNKNOWN_ERROR_CODE = 999; - - protected final Context context; - protected final AccountManager accountManager; - - public FxAccountAuthenticator(Context context) { - super(context); - this.context = context; - this.accountManager = AccountManager.get(context); - } - - @Override - public Bundle addAccount(AccountAuthenticatorResponse response, - String accountType, String authTokenType, String[] requiredFeatures, - Bundle options) - throws NetworkErrorException { - Logger.debug(LOG_TAG, "addAccount"); - - // The data associated to each Account should be invalidated when we change - // the set of Firefox Accounts on the system. - AndroidFxAccount.invalidateCaches(); - - final Bundle res = new Bundle(); - - if (!FxAccountConstants.ACCOUNT_TYPE.equals(accountType)) { - res.putInt(AccountManager.KEY_ERROR_CODE, -1); - res.putString(AccountManager.KEY_ERROR_MESSAGE, "Not adding unknown account type."); - return res; - } - - final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_GET_STARTED); - res.putParcelable(AccountManager.KEY_INTENT, intent); - return res; - } - - @Override - public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) - throws NetworkErrorException { - Logger.debug(LOG_TAG, "confirmCredentials"); - - return null; - } - - @Override - public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { - Logger.debug(LOG_TAG, "editProperties"); - - return null; - } - - protected static class Responder { - final AccountAuthenticatorResponse response; - final AndroidFxAccount fxAccount; - - public Responder(AccountAuthenticatorResponse response, AndroidFxAccount fxAccount) { - this.response = response; - this.fxAccount = fxAccount; - } - - public void fail(Exception e) { - Logger.warn(LOG_TAG, "Responding with error!", e); - fxAccount.releaseSharedAccountStateLock(); - final Bundle result = new Bundle(); - result.putInt(AccountManager.KEY_ERROR_CODE, UNKNOWN_ERROR_CODE); - result.putString(AccountManager.KEY_ERROR_MESSAGE, e.toString()); - response.onResult(result); - } - - public void succeed(String authToken) { - Logger.info(LOG_TAG, "Responding with success!"); - fxAccount.releaseSharedAccountStateLock(); - final Bundle result = new Bundle(); - result.putString(AccountManager.KEY_ACCOUNT_NAME, fxAccount.account.name); - result.putString(AccountManager.KEY_ACCOUNT_TYPE, fxAccount.account.type); - result.putString(AccountManager.KEY_AUTHTOKEN, authToken); - response.onResult(result); - } - } - - public abstract static class FxADefaultLoginStateMachineDelegate implements LoginStateMachineDelegate { - protected final Context context; - protected final AndroidFxAccount fxAccount; - protected final Executor executor; - protected final FxAccountClient client; - - public FxADefaultLoginStateMachineDelegate(Context context, AndroidFxAccount fxAccount) { - this.context = context; - this.fxAccount = fxAccount; - this.executor = Executors.newSingleThreadExecutor(); - this.client = new FxAccountClient20(fxAccount.getAccountServerURI(), executor); - } - - @Override - public FxAccountClient getClient() { - return client; - } - - @Override - public long getCertificateDurationInMilliseconds() { - return 12 * 60 * 60 * 1000; - } - - @Override - public long getAssertionDurationInMilliseconds() { - return 15 * 60 * 1000; - } - - @Override - public BrowserIDKeyPair generateKeyPair() throws NoSuchAlgorithmException { - return StateFactory.generateKeyPair(); - } - - @Override - public void handleTransition(Transition transition, State state) { - Logger.info(LOG_TAG, "handleTransition: " + transition + " to " + state.getStateLabel()); - } - - abstract public void handleNotMarried(State notMarried); - abstract public void handleMarried(Married married); - - @Override - public void handleFinal(State state) { - Logger.info(LOG_TAG, "handleFinal: in " + state.getStateLabel()); - fxAccount.setState(state); - // Update any notifications displayed. - final FxAccountNotificationManager notificationManager = new FxAccountNotificationManager(FxAccountSyncAdapter.NOTIFICATION_ID); - notificationManager.update(context, fxAccount); - - if (state.getStateLabel() != StateLabel.Married) { - handleNotMarried(state); - return; - } else { - handleMarried((Married) state); - } - } - } - - protected void getOAuthToken(final AccountAuthenticatorResponse response, final AndroidFxAccount fxAccount, final String scope) throws NetworkErrorException { - Logger.info(LOG_TAG, "Fetching oauth token with scope: " + scope); - - final Responder responder = new Responder(response, fxAccount); - final String oauthServerUri = fxAccount.getOAuthServerURI(); - - final String audience; - try { - audience = FxAccountUtils.getAudienceForURL(oauthServerUri); // The assertion gets traded in for an oauth bearer token. - } catch (Exception e) { - Logger.warn(LOG_TAG, "Got exception fetching oauth token.", e); - responder.fail(e); - return; - } - - final FxAccountLoginStateMachine stateMachine = new FxAccountLoginStateMachine(); - - stateMachine.advance(fxAccount.getState(), StateLabel.Married, new FxADefaultLoginStateMachineDelegate(context, fxAccount) { - @Override - public void handleNotMarried(State state) { - final String message = "Cannot fetch oauth token from state: " + state.getStateLabel(); - Logger.warn(LOG_TAG, message); - responder.fail(new RuntimeException(message)); - } - - @Override - public void handleMarried(final Married married) { - final String assertion; - try { - assertion = married.generateAssertion(audience, JSONWebTokenUtils.DEFAULT_ASSERTION_ISSUER); - if (FxAccountUtils.LOG_PERSONAL_INFORMATION) { - JSONWebTokenUtils.dumpAssertion(assertion); - } - } catch (Exception e) { - Logger.warn(LOG_TAG, "Got exception fetching oauth token.", e); - responder.fail(e); - return; - } - - final FxAccountOAuthClient10 oauthClient = new FxAccountOAuthClient10(oauthServerUri, executor); - Logger.debug(LOG_TAG, "OAuth fetch for scope: " + scope); - oauthClient.authorization(FxAccountConstants.OAUTH_CLIENT_ID_FENNEC, assertion, null, scope, new RequestDelegate<FxAccountOAuthClient10.AuthorizationResponse>() { - @Override - public void handleSuccess(AuthorizationResponse result) { - Logger.debug(LOG_TAG, "OAuth success."); - FxAccountUtils.pii(LOG_TAG, "Fetched oauth token: " + result.access_token); - responder.succeed(result.access_token); - } - - @Override - public void handleFailure(FxAccountAbstractClientRemoteException e) { - Logger.error(LOG_TAG, "OAuth failure.", e); - if (e.isInvalidAuthentication()) { - // We were married, generated an assertion, and our assertion was rejected by the - // oauth client. If it's a 401, we probably have a stale certificate. If instead of - // a stale certificate we have bad credentials, the state machine will fail to sign - // our public key and drive us back to Separated. - fxAccount.setState(married.makeCohabitingState()); - } - responder.fail(e); - } - - @Override - public void handleError(Exception e) { - Logger.error(LOG_TAG, "OAuth error.", e); - responder.fail(e); - } - }); - } - }); - } - - @Override - public Bundle getAuthToken(final AccountAuthenticatorResponse response, - final Account account, final String authTokenType, final Bundle options) - throws NetworkErrorException { - Logger.debug(LOG_TAG, "getAuthToken: " + authTokenType); - - // If we have a cached authToken, hand it over. - final String cachedAuthToken = AccountManager.get(context).peekAuthToken(account, authTokenType); - if (cachedAuthToken != null && !cachedAuthToken.isEmpty()) { - Logger.info(LOG_TAG, "Return cached token."); - final Bundle result = new Bundle(); - result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); - result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); - result.putString(AccountManager.KEY_AUTHTOKEN, cachedAuthToken); - return result; - } - - // If we're asked for an oauth::scope token, try to generate one. - final String oauthPrefix = "oauth::"; - if (authTokenType != null && authTokenType.startsWith(oauthPrefix)) { - final String scope = authTokenType.substring(oauthPrefix.length()); - final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account); - try { - fxAccount.acquireSharedAccountStateLock(LOG_TAG); - } catch (InterruptedException e) { - Logger.warn(LOG_TAG, "Could not acquire account state lock; return error bundle."); - final Bundle bundle = new Bundle(); - bundle.putInt(AccountManager.KEY_ERROR_CODE, 1); - bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "Could not acquire account state lock."); - return bundle; - } - getOAuthToken(response, fxAccount, scope); - return null; - } - - // Otherwise, fail. - Logger.warn(LOG_TAG, "Returning error bundle for getAuthToken with unknown token type."); - final Bundle bundle = new Bundle(); - bundle.putInt(AccountManager.KEY_ERROR_CODE, 2); - bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "Unknown token type: " + authTokenType); - return bundle; - } - - @Override - public String getAuthTokenLabel(String authTokenType) { - Logger.debug(LOG_TAG, "getAuthTokenLabel"); - - return null; - } - - @Override - public Bundle hasFeatures(AccountAuthenticatorResponse response, - Account account, String[] features) throws NetworkErrorException { - Logger.debug(LOG_TAG, "hasFeatures"); - - return null; - } - - @Override - public Bundle updateCredentials(AccountAuthenticatorResponse response, - Account account, String authTokenType, Bundle options) - throws NetworkErrorException { - Logger.debug(LOG_TAG, "updateCredentials"); - - return null; - } - - /** - * If the account is going to be removed, broadcast an "account deleted" - * intent. This allows us to clean up the account. - * <p> - * It is preferable to receive Android's LOGIN_ACCOUNTS_CHANGED_ACTION broadcast - * than to create our own hacky broadcast here, but that doesn't include enough - * information about which Accounts changed to correctly identify whether a Sync - * account has been removed (when some Firefox channels are installed on the SD - * card). We can work around this by storing additional state but it's both messy - * and expensive because the broadcast is noisy. - * <p> - * Note that this is <b>not</b> called when an Android Account is blown away - * due to the SD card being unmounted. - */ - @Override - public Bundle getAccountRemovalAllowed(final AccountAuthenticatorResponse response, Account account) - throws NetworkErrorException { - Bundle result = super.getAccountRemovalAllowed(response, account); - - if (result == null || - !result.containsKey(AccountManager.KEY_BOOLEAN_RESULT) || - result.containsKey(AccountManager.KEY_INTENT)) { - return result; - } - - final boolean removalAllowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT); - if (!removalAllowed) { - return result; - } - - // Broadcast a message to all Firefox channels sharing this Android - // Account type telling that this Firefox account has been deleted. - // - // Broadcast intents protected with permissions are secure, so it's okay - // to include private information such as a password. - final AndroidFxAccount androidFxAccount = new AndroidFxAccount(context, account); - - // Deleting the pickle file in a blocking manner will avoid race conditions that might happen when - // an account is unpickled while an FxAccount is being deleted. - // Also we have an assumption that this method is always called from a background thread, so we delete - // the pickle file directly without being afraid from a StrictMode violation. - ThreadUtils.assertNotOnUiThread(); - - final Intent serviceIntent = androidFxAccount.populateDeletedAccountIntent( - new Intent(context, FxAccountDeletedService.class) - ); - Logger.info(LOG_TAG, "Account named " + account.name + " being removed; " + - "starting FxAccountDeletedService with action: " + serviceIntent.getAction() + "."); - context.startService(serviceIntent); - - Logger.info(LOG_TAG, "Firefox account named " + account.name + " being removed; " + - "deleting saved pickle file '" + FxAccountConstants.ACCOUNT_PICKLE_FILENAME + "'."); - deletePickle(); - - return result; - } - - private void deletePickle() { - try { - AccountPickler.deletePickle(context, FxAccountConstants.ACCOUNT_PICKLE_FILENAME); - } catch (Exception e) { - // This should never happen, but we really don't want to die in a background thread. - Logger.warn(LOG_TAG, "Got exception deleting saved pickle file; ignoring.", e); - } - } -} |