mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1142596 - Use cached FxA OAuth tokens in Reading List sync. r=rnewman
========9b406122ef
Author: Nick Alexander <nalexander@mozilla.com> Bug 1142596 - Post: Use production OAuth and Reading List endpoints. ========9e5368b4aa
Author: Nick Alexander <nalexander@mozilla.com> Date: Tue Mar 24 23:00:34 2015 -0700 Bug 1142596 - Part 4: Make ReadingListSyncAdapter use oauth tokens produced and cached by the authenticator. ========fbef93698d
Author: Nick Alexander <nalexander@mozilla.com> Date: Tue Mar 24 22:49:52 2015 -0700 Bug 1142596 - Part 3: Implement getAuthToken with token types of the form oauth::scope. Be aware that there are two levels of token invalidation relevant here. The first level is when a consumer uses an oauth token and gets a 401; in this case, the consumer *must* call Android's own invalidateAuthToken. The second level is when the oauth client itself gets a 401 trying to fetch an oauth token; in this case, the internal state of the Firefox Account needs to be pushed back. ========e4e2247b4e
Author: Nick Alexander <nalexander@mozilla.com> Date: Tue Mar 24 22:43:26 2015 -0700 Bug 1142596 - Part 2: Extract login state machine delegate encapsulating expirations. ========f1f716cc88
Author: Nick Alexander <nalexander@mozilla.com> Date: Tue Mar 24 22:14:47 2015 -0700 Bug 1142596 - Part 1: Surface Reading List authentication errors. ========5833cbbf71
Author: Nick Alexander <nalexander@mozilla.com> Date: Tue Mar 24 22:01:46 2015 -0700 Bug 1142596 - Pre: Add note about deleting cached oauth tokens. Deleting cached oauth tokens is tracked by Bug 1147245. ========b0165a6c14
Author: Nick Alexander <nalexander@mozilla.com> Date: Tue Mar 24 23:06:49 2015 -0700 Bug 1142596 - Pre: Trim imports.
This commit is contained in:
parent
3b37971140
commit
32984be1dd
@ -868,6 +868,7 @@ sync_java_files = [
|
||||
'fxa/authenticator/FxAccountAuthenticatorService.java',
|
||||
'fxa/authenticator/FxAccountLoginDelegate.java',
|
||||
'fxa/authenticator/FxAccountLoginException.java',
|
||||
'fxa/authenticator/FxADefaultLoginStateMachineDelegate.java',
|
||||
'fxa/FirefoxAccounts.java',
|
||||
'fxa/FxAccountConstants.java',
|
||||
'fxa/login/BaseRequestDelegate.java',
|
||||
@ -1171,6 +1172,7 @@ reading_list_service_java_files = [
|
||||
'reading/ReadingListClientRecordFactory.java',
|
||||
'reading/ReadingListConstants.java',
|
||||
'reading/ReadingListDeleteDelegate.java',
|
||||
'reading/ReadingListInvalidAuthenticationException.java',
|
||||
'reading/ReadingListRecord.java',
|
||||
'reading/ReadingListRecordDelegate.java',
|
||||
'reading/ReadingListRecordResponse.java',
|
||||
|
@ -14,7 +14,6 @@ import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.R.string;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.nativecode.NativeCrypto;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
@ -23,7 +22,6 @@ import org.mozilla.gecko.sync.crypto.KeyBundle;
|
||||
import org.mozilla.gecko.sync.crypto.PBKDF2;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
|
||||
public class FxAccountUtils {
|
||||
private static final String LOG_TAG = FxAccountUtils.class.getSimpleName();
|
||||
|
@ -9,6 +9,7 @@ import org.mozilla.gecko.sync.HTTPFailureException;
|
||||
import org.mozilla.gecko.sync.net.SyncStorageResponse;
|
||||
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.HttpStatus;
|
||||
|
||||
/**
|
||||
* From <a href="https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md">https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md</a>.
|
||||
@ -51,6 +52,10 @@ public class FxAccountAbstractClientException extends Exception {
|
||||
public String toString() {
|
||||
return "<FxAccountAbstractClientRemoteException " + this.httpStatusCode + " [" + this.apiErrorNumber + "]: " + this.message + ">";
|
||||
}
|
||||
|
||||
public boolean isInvalidAuthentication() {
|
||||
return this.httpStatusCode == HttpStatus.SC_UNAUTHORIZED;
|
||||
}
|
||||
}
|
||||
|
||||
public static class FxAccountAbstractClientMalformedResponseException extends FxAccountAbstractClientRemoteException {
|
||||
|
@ -0,0 +1,84 @@
|
||||
/* 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 java.security.NoSuchAlgorithmException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
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.browserid.BrowserIDKeyPair;
|
||||
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.sync.FxAccountNotificationManager;
|
||||
import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public abstract class FxADefaultLoginStateMachineDelegate implements LoginStateMachineDelegate {
|
||||
protected final static String LOG_TAG = LoginStateMachineDelegate.class.getSimpleName();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
abstract public void handleNotMarried(State notMarried);
|
||||
abstract public void handleMarried(Married married);
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
@ -4,9 +4,31 @@
|
||||
|
||||
package org.mozilla.gecko.fxa.authenticator;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
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.activities.FxAccountGetStartedActivity;
|
||||
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.sync.FxAccountNotificationManager;
|
||||
import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter;
|
||||
|
||||
import android.accounts.AbstractAccountAuthenticator;
|
||||
import android.accounts.Account;
|
||||
@ -19,6 +41,7 @@ import android.os.Bundle;
|
||||
|
||||
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;
|
||||
@ -68,12 +91,190 @@ public class FxAccountAuthenticator extends AbstractAccountAuthenticator {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected static class Responder {
|
||||
final AccountAuthenticatorResponse response;
|
||||
final Account account;
|
||||
|
||||
public Responder(AccountAuthenticatorResponse response, Account account) {
|
||||
this.response = response;
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
public void fail(Exception e) {
|
||||
Logger.warn(LOG_TAG, "Responding with error!", e);
|
||||
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!");
|
||||
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, 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.getAndroidAccount());
|
||||
|
||||
final String oauthServerUri = FxAccountConstants.DEFAULT_OAUTH_SERVER_ENDPOINT;
|
||||
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");
|
||||
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);
|
||||
getOAuthToken(response, fxAccount, scope);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Otherwise, fail.
|
||||
Logger.warn(LOG_TAG, "Returning null bundle for getAuthToken.");
|
||||
|
||||
return null;
|
||||
|
@ -68,6 +68,10 @@ public class FxAccountDeletedService extends IntentService {
|
||||
|
||||
// Remove any displayed notifications.
|
||||
new FxAccountNotificationManager(FxAccountSyncAdapter.NOTIFICATION_ID).clear(context);
|
||||
|
||||
// Bug 1147275: Delete cached oauth tokens. There's no way to query all
|
||||
// oauth tokens from Android, so this is tricky to do comprehensively. We
|
||||
// can query, individually, for specific oauth tokens to delete, however.
|
||||
}
|
||||
|
||||
public static void deletePickle(final Context context) {
|
||||
|
@ -65,7 +65,7 @@ public class FxAccountNotificationManager {
|
||||
* @param fxAccount
|
||||
* Firefox Account to reflect to the notification manager.
|
||||
*/
|
||||
protected void update(Context context, AndroidFxAccount fxAccount) {
|
||||
public void update(Context context, AndroidFxAccount fxAccount) {
|
||||
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
final State state = fxAccount.getState();
|
||||
|
@ -6,7 +6,6 @@ package org.mozilla.gecko.fxa.sync;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
@ -15,24 +14,19 @@ import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
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.SkewHandler;
|
||||
import org.mozilla.gecko.browserid.BrowserIDKeyPair;
|
||||
import org.mozilla.gecko.browserid.JSONWebTokenUtils;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.authenticator.AccountPickler;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.authenticator.FxADefaultLoginStateMachineDelegate;
|
||||
import org.mozilla.gecko.fxa.authenticator.FxAccountAuthenticator;
|
||||
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.sync.BackoffHandler;
|
||||
import org.mozilla.gecko.sync.GlobalSession;
|
||||
import org.mozilla.gecko.sync.PrefsBackoffHandler;
|
||||
@ -456,38 +450,17 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
// and extend the background delay even further into the future.
|
||||
schedulePolicy.configureBackoffMillisBeforeSyncing(rateLimitBackoffHandler, backgroundBackoffHandler);
|
||||
|
||||
final String authServerEndpoint = fxAccount.getAccountServerURI();
|
||||
final String tokenServerEndpoint = fxAccount.getTokenServerURI();
|
||||
final URI tokenServerEndpointURI = new URI(tokenServerEndpoint);
|
||||
final String audience = FxAccountUtils.getAudienceForURL(tokenServerEndpoint);
|
||||
|
||||
// TODO: why doesn't the loginPolicy extract the audience from the account?
|
||||
final FxAccountClient client = new FxAccountClient20(authServerEndpoint, executor);
|
||||
final FxAccountLoginStateMachine stateMachine = new FxAccountLoginStateMachine();
|
||||
stateMachine.advance(state, StateLabel.Married, new LoginStateMachineDelegate() {
|
||||
stateMachine.advance(state, StateLabel.Married, new FxADefaultLoginStateMachineDelegate(context, fxAccount) {
|
||||
@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());
|
||||
public void handleNotMarried(State notMarried) {
|
||||
Logger.info(LOG_TAG, "handleNotMarried: in " + notMarried.getStateLabel());
|
||||
schedulePolicy.onHandleFinal(notMarried.getNeededAction());
|
||||
syncDelegate.handleCannotSync(notMarried);
|
||||
}
|
||||
|
||||
private boolean shouldRequestToken(final BackoffHandler tokenBackoffHandler, final Bundle extras) {
|
||||
@ -495,18 +468,11 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFinal(State state) {
|
||||
Logger.info(LOG_TAG, "handleFinal: in " + state.getStateLabel());
|
||||
fxAccount.setState(state);
|
||||
schedulePolicy.onHandleFinal(state.getNeededAction());
|
||||
notificationManager.update(context, fxAccount);
|
||||
try {
|
||||
if (state.getStateLabel() != StateLabel.Married) {
|
||||
syncDelegate.handleCannotSync(state);
|
||||
return;
|
||||
}
|
||||
public void handleMarried(Married married) {
|
||||
schedulePolicy.onHandleFinal(married.getNeededAction());
|
||||
Logger.info(LOG_TAG, "handleMarried: in " + married.getStateLabel());
|
||||
|
||||
final Married married = (Married) state;
|
||||
try {
|
||||
final String assertion = married.generateAssertion(audience, JSONWebTokenUtils.DEFAULT_ASSERTION_ISSUER);
|
||||
|
||||
/*
|
||||
|
@ -0,0 +1,18 @@
|
||||
/* 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.reading;
|
||||
|
||||
import org.mozilla.gecko.sync.net.MozResponse;
|
||||
|
||||
public class ReadingListInvalidAuthenticationException extends Exception {
|
||||
private static final long serialVersionUID = 7112459541558266597L;
|
||||
|
||||
public final MozResponse response;
|
||||
|
||||
public ReadingListInvalidAuthenticationException(MozResponse response) {
|
||||
super();
|
||||
this.response = response;
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@ package org.mozilla.gecko.reading;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
@ -15,30 +14,15 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.mozilla.gecko.background.common.PrefsBranch;
|
||||
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.db.BrowserContract.ReadingListItems;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
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.sync.FxAccountSyncDelegate;
|
||||
import org.mozilla.gecko.sync.net.AuthHeaderProvider;
|
||||
import org.mozilla.gecko.sync.net.BearerAuthHeaderProvider;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.content.AbstractThreadedSyncAdapter;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentResolver;
|
||||
@ -59,8 +43,7 @@ public class ReadingListSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
this.executor = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
|
||||
|
||||
static final class SyncAdapterSynchronizerDelegate implements ReadingListSynchronizerDelegate {
|
||||
protected static abstract class SyncAdapterSynchronizerDelegate implements ReadingListSynchronizerDelegate {
|
||||
private final FxAccountSyncDelegate syncDelegate;
|
||||
private final ContentProviderClient cpc;
|
||||
private final SyncResult result;
|
||||
@ -73,9 +56,14 @@ public class ReadingListSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
abstract public void onInvalidAuthentication();
|
||||
|
||||
@Override
|
||||
public void onUnableToSync(Exception e) {
|
||||
Logger.warn(LOG_TAG, "Unable to sync.", e);
|
||||
if (e instanceof ReadingListInvalidAuthenticationException) {
|
||||
onInvalidAuthentication();
|
||||
}
|
||||
cpc.release();
|
||||
syncDelegate.handleError(e);
|
||||
}
|
||||
@ -120,6 +108,55 @@ public class ReadingListSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
private void syncWithAuthorization(final Context context,
|
||||
final Account account,
|
||||
final SyncResult syncResult,
|
||||
final FxAccountSyncDelegate syncDelegate,
|
||||
final String authToken,
|
||||
final SharedPreferences sharedPrefs,
|
||||
final Bundle extras) {
|
||||
final AuthHeaderProvider auth = new BearerAuthHeaderProvider(authToken);
|
||||
|
||||
final String endpointString = ReadingListConstants.DEFAULT_PROD_ENDPOINT;
|
||||
final URI endpoint;
|
||||
Logger.info(LOG_TAG, "Syncing reading list against " + endpointString);
|
||||
try {
|
||||
endpoint = new URI(endpointString);
|
||||
} catch (URISyntaxException e) {
|
||||
// Should never happen.
|
||||
Logger.error(LOG_TAG, "Unexpected malformed URI for reading list service: " + endpointString);
|
||||
syncDelegate.handleError(e);
|
||||
return;
|
||||
}
|
||||
|
||||
final PrefsBranch branch = new PrefsBranch(sharedPrefs, "readinglist.");
|
||||
final ReadingListClient remote = new ReadingListClient(endpoint, auth);
|
||||
final ContentProviderClient cpc = getContentProviderClient(context); // Released by the inner SyncAdapterSynchronizerDelegate.
|
||||
|
||||
final LocalReadingListStorage local = new LocalReadingListStorage(cpc);
|
||||
String localName = branch.getString(PREF_LOCAL_NAME, null);
|
||||
if (localName == null) {
|
||||
localName = FxAccountUtils.defaultClientName(context);
|
||||
}
|
||||
|
||||
// Make sure DB rows don't refer to placeholder values.
|
||||
local.updateLocalNames(localName);
|
||||
|
||||
final ReadingListSynchronizer synchronizer = new ReadingListSynchronizer(branch, remote, local);
|
||||
|
||||
synchronizer.syncAll(new SyncAdapterSynchronizerDelegate(syncDelegate, cpc, syncResult) {
|
||||
@Override
|
||||
public void onInvalidAuthentication() {
|
||||
// The reading list server rejected our oauth token! Invalidate it. Next
|
||||
// time through, we'll request a new one, which will drive the login
|
||||
// state machine, produce a new assertion, and eventually a fresh token.
|
||||
Logger.info(LOG_TAG, "Invalidating oauth token after 401!");
|
||||
AccountManager.get(context).invalidateAuthToken(account.type, authToken);
|
||||
}
|
||||
});
|
||||
// TODO: backoffs, and everything else handled by a SessionCallback.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPerformSync(final Account account, final Bundle extras, final String authority, final ContentProviderClient provider, final SyncResult syncResult) {
|
||||
Logger.setThreadLogTag(ReadingListConstants.GLOBAL_LOG_TAG);
|
||||
@ -128,150 +165,31 @@ public class ReadingListSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
final Context context = getContext();
|
||||
final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
|
||||
|
||||
// If this sync was triggered by user action, this will be true.
|
||||
final boolean isImmediate = (extras != null) &&
|
||||
(extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false) ||
|
||||
extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false));
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final FxAccountSyncDelegate syncDelegate = new FxAccountSyncDelegate(latch, syncResult, fxAccount);
|
||||
|
||||
final AccountManager accountManager = AccountManager.get(context);
|
||||
// If we have an auth failure that requires user intervention, FxA will show system
|
||||
// notifications prompting the user to re-connect as it advances the internal account state.
|
||||
// true causes the auth token fetch to return null on failure immediately, rather than doing
|
||||
// Mysterious Internal Work to try to get the token.
|
||||
final boolean notifyAuthFailure = true;
|
||||
try {
|
||||
final State state;
|
||||
try {
|
||||
state = fxAccount.getState();
|
||||
} catch (Exception e) {
|
||||
Logger.error(LOG_TAG, "Unable to sync.", e);
|
||||
return;
|
||||
final String authToken = accountManager.blockingGetAuthToken(account, ReadingListConstants.AUTH_TOKEN_TYPE, notifyAuthFailure);
|
||||
if (authToken == null) {
|
||||
throw new RuntimeException("Couldn't get oauth token! Aborting sync.");
|
||||
}
|
||||
|
||||
final String oauthServerUri = FxAccountConstants.STAGE_OAUTH_SERVER_ENDPOINT;
|
||||
final String authServerEndpoint = fxAccount.getAccountServerURI();
|
||||
final String audience = FxAccountUtils.getAudienceForURL(oauthServerUri); // The assertion gets traded in for an oauth bearer token.
|
||||
|
||||
final SharedPreferences sharedPrefs = fxAccount.getReadingListPrefs();
|
||||
final FxAccountClient client = new FxAccountClient20(authServerEndpoint, executor);
|
||||
final FxAccountLoginStateMachine stateMachine = new FxAccountLoginStateMachine();
|
||||
|
||||
stateMachine.advance(state, StateLabel.Married, new LoginStateMachineDelegate() {
|
||||
@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());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFinal(State state) {
|
||||
Logger.info(LOG_TAG, "handleFinal: in " + state.getStateLabel());
|
||||
fxAccount.setState(state);
|
||||
|
||||
// TODO: scheduling, notifications.
|
||||
try {
|
||||
if (state.getStateLabel() != StateLabel.Married) {
|
||||
syncDelegate.handleCannotSync(state);
|
||||
return;
|
||||
}
|
||||
|
||||
final Married married = (Married) state;
|
||||
final String assertion = married.generateAssertion(audience, JSONWebTokenUtils.DEFAULT_ASSERTION_ISSUER);
|
||||
JSONWebTokenUtils.dumpAssertion(assertion);
|
||||
|
||||
final String clientID = FxAccountConstants.OAUTH_CLIENT_ID_FENNEC;
|
||||
final String scope = ReadingListConstants.OAUTH_SCOPE_READINGLIST;
|
||||
syncWithAssertion(clientID, scope, assertion, sharedPrefs, extras);
|
||||
} catch (Exception e) {
|
||||
syncDelegate.handleError(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void syncWithAssertion(final String client_id, final String scope, final String assertion,
|
||||
final SharedPreferences sharedPrefs, final Bundle extras) {
|
||||
final FxAccountOAuthClient10 oauthClient = new FxAccountOAuthClient10(oauthServerUri, executor);
|
||||
Logger.debug(LOG_TAG, "OAuth fetch.");
|
||||
oauthClient.authorization(client_id, assertion, null, scope, new RequestDelegate<FxAccountOAuthClient10.AuthorizationResponse>() {
|
||||
@Override
|
||||
public void handleSuccess(AuthorizationResponse result) {
|
||||
Logger.debug(LOG_TAG, "OAuth success.");
|
||||
syncWithAuthorization(result, sharedPrefs, extras);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFailure(FxAccountAbstractClientRemoteException e) {
|
||||
Logger.error(LOG_TAG, "OAuth failure.", e);
|
||||
syncDelegate.handleError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(Exception e) {
|
||||
Logger.error(LOG_TAG, "OAuth error.", e);
|
||||
syncDelegate.handleError(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void syncWithAuthorization(AuthorizationResponse authResponse,
|
||||
SharedPreferences sharedPrefs,
|
||||
Bundle extras) {
|
||||
final AuthHeaderProvider auth = new BearerAuthHeaderProvider(authResponse.access_token);
|
||||
|
||||
final String endpointString = ReadingListConstants.DEFAULT_DEV_ENDPOINT;
|
||||
final URI endpoint;
|
||||
Logger.info(LOG_TAG, "XXX Syncing to " + endpointString);
|
||||
try {
|
||||
endpoint = new URI(endpointString);
|
||||
} catch (URISyntaxException e) {
|
||||
// Should never happen.
|
||||
Logger.error(LOG_TAG, "Unexpected malformed URI for reading list service: " + endpointString);
|
||||
syncDelegate.handleError(e);
|
||||
return;
|
||||
}
|
||||
|
||||
final PrefsBranch branch = new PrefsBranch(sharedPrefs, "readinglist.");
|
||||
final ReadingListClient remote = new ReadingListClient(endpoint, auth);
|
||||
final ContentProviderClient cpc = getContentProviderClient(context); // TODO: make sure I'm always released!
|
||||
|
||||
final LocalReadingListStorage local = new LocalReadingListStorage(cpc);
|
||||
String localName = branch.getString(PREF_LOCAL_NAME, null);
|
||||
if (localName == null) {
|
||||
localName = FxAccountUtils.defaultClientName(context);
|
||||
}
|
||||
|
||||
// Make sure DB rows don't refer to placeholder values.
|
||||
local.updateLocalNames(localName);
|
||||
|
||||
final ReadingListSynchronizer synchronizer = new ReadingListSynchronizer(branch, remote, local);
|
||||
|
||||
synchronizer.syncAll(new SyncAdapterSynchronizerDelegate(syncDelegate, cpc, syncResult));
|
||||
// TODO: backoffs, and everything else handled by a SessionCallback.
|
||||
}
|
||||
});
|
||||
syncWithAuthorization(context, account, syncResult, syncDelegate, authToken, sharedPrefs, extras);
|
||||
|
||||
latch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
Logger.info(LOG_TAG, "Reading list sync done.");
|
||||
|
||||
} catch (Exception e) {
|
||||
// We can get lots of exceptions here; handle them uniformly.
|
||||
Logger.error(LOG_TAG, "Got error syncing.", e);
|
||||
syncDelegate.handleError(e);
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* * Account error notifications. How do we avoid these overlapping with Sync?
|
||||
@ -290,7 +208,6 @@ public class ReadingListSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
private ContentProviderClient getContentProviderClient(Context context) {
|
||||
final ContentResolver contentResolver = context.getContentResolver();
|
||||
final ContentProviderClient client = contentResolver.acquireContentProviderClient(ReadingListItems.CONTENT_URI);
|
||||
|
@ -774,7 +774,11 @@ public class ReadingListSynchronizer {
|
||||
final int statusCode = response.getStatusCode();
|
||||
Logger.error(LOG_TAG, "Download failed. since = " + since + ". Response: " + statusCode);
|
||||
response.logResponseBody(LOG_TAG);
|
||||
delegate.fail();
|
||||
if (response.isInvalidAuthentication()) {
|
||||
delegate.fail(new ReadingListInvalidAuthenticationException(response));
|
||||
} else {
|
||||
delegate.fail();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -19,6 +19,7 @@ import org.mozilla.gecko.sync.NonObjectJSONException;
|
||||
import ch.boye.httpclientandroidlib.Header;
|
||||
import ch.boye.httpclientandroidlib.HttpEntity;
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.HttpStatus;
|
||||
import ch.boye.httpclientandroidlib.impl.cookie.DateParseException;
|
||||
import ch.boye.httpclientandroidlib.impl.cookie.DateUtils;
|
||||
|
||||
@ -42,6 +43,10 @@ public class MozResponse {
|
||||
return this.getStatusCode() == 200;
|
||||
}
|
||||
|
||||
public boolean isInvalidAuthentication() {
|
||||
return this.getStatusCode() == HttpStatus.SC_UNAUTHORIZED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the content type of the HTTP response body.
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user