mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
590 lines
24 KiB
Java
590 lines
24 KiB
Java
/* 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.sync;
|
|
|
|
import java.net.URI;
|
|
import java.net.URISyntaxException;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.util.concurrent.CountDownLatch;
|
|
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.SkewHandler;
|
|
import org.mozilla.gecko.browserid.BrowserIDKeyPair;
|
|
import org.mozilla.gecko.browserid.JSONWebTokenUtils;
|
|
import org.mozilla.gecko.browserid.RSACryptoImplementation;
|
|
import org.mozilla.gecko.browserid.verifier.BrowserIDRemoteVerifierClient;
|
|
import org.mozilla.gecko.browserid.verifier.BrowserIDVerifierDelegate;
|
|
import org.mozilla.gecko.fxa.FxAccountConstants;
|
|
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
|
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.sync.BackoffHandler;
|
|
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
|
import org.mozilla.gecko.sync.GlobalSession;
|
|
import org.mozilla.gecko.sync.PrefsBackoffHandler;
|
|
import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
|
|
import org.mozilla.gecko.sync.SyncConfiguration;
|
|
import org.mozilla.gecko.sync.Utils;
|
|
import org.mozilla.gecko.sync.crypto.KeyBundle;
|
|
import org.mozilla.gecko.sync.delegates.BaseGlobalSessionCallback;
|
|
import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
|
|
import org.mozilla.gecko.sync.net.AuthHeaderProvider;
|
|
import org.mozilla.gecko.sync.net.HawkAuthHeaderProvider;
|
|
import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
|
|
import org.mozilla.gecko.tokenserver.TokenServerClient;
|
|
import org.mozilla.gecko.tokenserver.TokenServerClientDelegate;
|
|
import org.mozilla.gecko.tokenserver.TokenServerException;
|
|
import org.mozilla.gecko.tokenserver.TokenServerToken;
|
|
|
|
import android.accounts.Account;
|
|
import android.content.AbstractThreadedSyncAdapter;
|
|
import android.content.ContentProviderClient;
|
|
import android.content.ContentResolver;
|
|
import android.content.Context;
|
|
import android.content.SharedPreferences;
|
|
import android.content.SyncResult;
|
|
import android.os.Bundle;
|
|
import android.os.SystemClock;
|
|
|
|
public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
|
|
private static final String LOG_TAG = FxAccountSyncAdapter.class.getSimpleName();
|
|
|
|
public static final int NOTIFICATION_ID = LOG_TAG.hashCode();
|
|
|
|
// Tracks the last seen storage hostname for backoff purposes.
|
|
private static final String PREF_BACKOFF_STORAGE_HOST = "backoffStorageHost";
|
|
|
|
// Used to do cheap in-memory rate limiting.
|
|
private static final int MINIMUM_SYNC_DELAY_MILLIS = 5000;
|
|
private volatile long lastSyncRealtimeMillis = 0L;
|
|
|
|
protected final ExecutorService executor;
|
|
protected final FxAccountNotificationManager notificationManager;
|
|
|
|
public FxAccountSyncAdapter(Context context, boolean autoInitialize) {
|
|
super(context, autoInitialize);
|
|
this.executor = Executors.newSingleThreadExecutor();
|
|
this.notificationManager = new FxAccountNotificationManager(NOTIFICATION_ID);
|
|
}
|
|
|
|
protected static class SyncDelegate {
|
|
protected final Context context;
|
|
protected final CountDownLatch latch;
|
|
protected final SyncResult syncResult;
|
|
protected final AndroidFxAccount fxAccount;
|
|
protected final FxAccountNotificationManager notificationManager;
|
|
|
|
public SyncDelegate(Context context, CountDownLatch latch, SyncResult syncResult, AndroidFxAccount fxAccount,
|
|
FxAccountNotificationManager notificationManager) {
|
|
if (context == null) {
|
|
throw new IllegalArgumentException("context must not be null");
|
|
}
|
|
if (latch == null) {
|
|
throw new IllegalArgumentException("latch must not be null");
|
|
}
|
|
if (syncResult == null) {
|
|
throw new IllegalArgumentException("syncResult must not be null");
|
|
}
|
|
if (fxAccount == null) {
|
|
throw new IllegalArgumentException("fxAccount must not be null");
|
|
}
|
|
if (notificationManager == null) {
|
|
throw new IllegalArgumentException("notificationManager must not be null");
|
|
}
|
|
this.context = context;
|
|
this.latch = latch;
|
|
this.syncResult = syncResult;
|
|
this.fxAccount = fxAccount;
|
|
this.notificationManager = notificationManager;
|
|
}
|
|
|
|
/**
|
|
* No error! Say that we made progress.
|
|
*/
|
|
protected void setSyncResultSuccess() {
|
|
syncResult.stats.numUpdates += 1;
|
|
}
|
|
|
|
/**
|
|
* Soft error. Say that we made progress, so that Android will sync us again
|
|
* after exponential backoff.
|
|
*/
|
|
protected void setSyncResultSoftError() {
|
|
syncResult.stats.numUpdates += 1;
|
|
syncResult.stats.numIoExceptions += 1;
|
|
}
|
|
|
|
/**
|
|
* Hard error. We don't want Android to sync us again, even if we make
|
|
* progress, until the user intervenes.
|
|
*/
|
|
protected void setSyncResultHardError() {
|
|
syncResult.stats.numAuthExceptions += 1;
|
|
}
|
|
|
|
public void handleSuccess() {
|
|
Logger.info(LOG_TAG, "Sync succeeded.");
|
|
setSyncResultSuccess();
|
|
latch.countDown();
|
|
}
|
|
|
|
public void handleError(Exception e) {
|
|
Logger.error(LOG_TAG, "Got exception syncing.", e);
|
|
setSyncResultSoftError();
|
|
// This is awful, but we need to propagate bad assertions back up the
|
|
// chain somehow, and this will do for now.
|
|
if (e instanceof TokenServerException) {
|
|
// We should only get here *after* we're locked into the married state.
|
|
State state = fxAccount.getState();
|
|
if (state.getStateLabel() == StateLabel.Married) {
|
|
Married married = (Married) state;
|
|
fxAccount.setState(married.makeCohabitingState());
|
|
}
|
|
}
|
|
latch.countDown();
|
|
}
|
|
|
|
/**
|
|
* When the login machine terminates, we might not be in the
|
|
* <code>Married</code> state, and therefore we can't sync. This method
|
|
* messages as much to the user.
|
|
* <p>
|
|
* To avoid stopping us syncing altogether, we set a soft error rather than
|
|
* a hard error. In future, we would like to set a hard error if we are in,
|
|
* for example, the <code>Separated</code> state, and then have some user
|
|
* initiated activity mark the Android account as ready to sync again. This
|
|
* is tricky, though, so we play it safe for now.
|
|
*
|
|
* @param finalState
|
|
* that login machine ended in.
|
|
*/
|
|
public void handleCannotSync(State finalState) {
|
|
Logger.warn(LOG_TAG, "Cannot sync from state: " + finalState.getStateLabel());
|
|
setSyncResultSoftError();
|
|
latch.countDown();
|
|
}
|
|
|
|
public void postponeSync(long millis) {
|
|
if (millis <= 0) {
|
|
Logger.debug(LOG_TAG, "Asked to postpone sync, but zero delay. Short-circuiting.");
|
|
} else {
|
|
// delayUntil is broken: https://code.google.com/p/android/issues/detail?id=65669
|
|
// So we don't bother doing this. Instead, we rely on the periodic sync
|
|
// we schedule, and the backoff handler for the rest.
|
|
/*
|
|
Logger.warn(LOG_TAG, "Postponing sync by " + millis + "ms.");
|
|
syncResult.delayUntil = millis / 1000;
|
|
*/
|
|
}
|
|
setSyncResultSoftError();
|
|
latch.countDown();
|
|
}
|
|
}
|
|
|
|
protected static class SessionCallback implements BaseGlobalSessionCallback {
|
|
protected final SyncDelegate syncDelegate;
|
|
protected final SchedulePolicy schedulePolicy;
|
|
protected volatile BackoffHandler storageBackoffHandler;
|
|
|
|
public SessionCallback(SyncDelegate syncDelegate, SchedulePolicy schedulePolicy) {
|
|
this.syncDelegate = syncDelegate;
|
|
this.schedulePolicy = schedulePolicy;
|
|
}
|
|
|
|
public void setBackoffHandler(BackoffHandler backoffHandler) {
|
|
this.storageBackoffHandler = backoffHandler;
|
|
}
|
|
|
|
@Override
|
|
public boolean shouldBackOffStorage() {
|
|
return storageBackoffHandler.delayMilliseconds() > 0;
|
|
}
|
|
|
|
@Override
|
|
public void requestBackoff(long backoffMillis) {
|
|
final boolean onlyExtend = true; // Because we trust what the storage server says.
|
|
schedulePolicy.configureBackoffMillisOnBackoff(storageBackoffHandler, backoffMillis, onlyExtend);
|
|
}
|
|
|
|
@Override
|
|
public void informUpgradeRequiredResponse(GlobalSession session) {
|
|
schedulePolicy.onUpgradeRequired();
|
|
}
|
|
|
|
@Override
|
|
public void informUnauthorizedResponse(GlobalSession globalSession, URI oldClusterURL) {
|
|
schedulePolicy.onUnauthorized();
|
|
}
|
|
|
|
@Override
|
|
public void handleStageCompleted(Stage currentState, GlobalSession globalSession) {
|
|
}
|
|
|
|
@Override
|
|
public void handleSuccess(GlobalSession globalSession) {
|
|
Logger.info(LOG_TAG, "Global session succeeded.");
|
|
|
|
// Get the number of clients, so we can schedule the sync interval accordingly.
|
|
try {
|
|
int otherClientsCount = globalSession.getClientsDelegate().getClientsCount();
|
|
Logger.debug(LOG_TAG, "" + otherClientsCount + " other client(s).");
|
|
this.schedulePolicy.onSuccessfulSync(otherClientsCount);
|
|
} finally {
|
|
// Continue with the usual success flow.
|
|
syncDelegate.handleSuccess();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void handleError(GlobalSession globalSession, Exception e) {
|
|
Logger.warn(LOG_TAG, "Global session failed."); // Exception will be dumped by delegate below.
|
|
syncDelegate.handleError(e);
|
|
// TODO: should we reduce the periodic sync interval?
|
|
}
|
|
|
|
@Override
|
|
public void handleAborted(GlobalSession globalSession, String reason) {
|
|
Logger.warn(LOG_TAG, "Global session aborted: " + reason);
|
|
syncDelegate.handleError(null);
|
|
// TODO: should we reduce the periodic sync interval?
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Return true if the provided {@link BackoffHandler} isn't reporting that we're in
|
|
* a backoff state, or the provided {@link Bundle} contains flags that indicate
|
|
* we should force a sync.
|
|
*/
|
|
private boolean shouldPerformSync(final BackoffHandler backoffHandler, final String kind, final Bundle extras) {
|
|
final long delay = backoffHandler.delayMilliseconds();
|
|
if (delay <= 0) {
|
|
return true;
|
|
}
|
|
|
|
if (extras == null) {
|
|
return false;
|
|
}
|
|
|
|
final boolean forced = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
|
|
if (forced) {
|
|
Logger.info(LOG_TAG, "Forced sync (" + kind + "): overruling remaining backoff of " + delay + "ms.");
|
|
} else {
|
|
Logger.info(LOG_TAG, "Not syncing (" + kind + "): must wait another " + delay + "ms.");
|
|
}
|
|
return forced;
|
|
}
|
|
|
|
protected void syncWithAssertion(final String audience,
|
|
final String assertion,
|
|
final URI tokenServerEndpointURI,
|
|
final BackoffHandler tokenBackoffHandler,
|
|
final SharedPreferences sharedPrefs,
|
|
final KeyBundle syncKeyBundle,
|
|
final String clientState,
|
|
final SessionCallback callback,
|
|
final Bundle extras) {
|
|
final TokenServerClientDelegate delegate = new TokenServerClientDelegate() {
|
|
private boolean didReceiveBackoff = false;
|
|
|
|
@Override
|
|
public void handleSuccess(final TokenServerToken token) {
|
|
FxAccountConstants.pii(LOG_TAG, "Got token! uid is " + token.uid + " and endpoint is " + token.endpoint + ".");
|
|
|
|
if (!didReceiveBackoff) {
|
|
// We must be OK to touch this token server.
|
|
tokenBackoffHandler.setEarliestNextRequest(0L);
|
|
}
|
|
|
|
final URI storageServerURI;
|
|
try {
|
|
storageServerURI = new URI(token.endpoint);
|
|
} catch (URISyntaxException e) {
|
|
handleError(e);
|
|
return;
|
|
}
|
|
final String storageHostname = storageServerURI.getHost();
|
|
|
|
// We back off on a per-host basis. When we have an endpoint URI from a token, we
|
|
// can check on the backoff status for that host.
|
|
// If we're supposed to be backing off, we abort the not-yet-started session.
|
|
final BackoffHandler storageBackoffHandler = new PrefsBackoffHandler(sharedPrefs, "sync.storage");
|
|
callback.setBackoffHandler(storageBackoffHandler);
|
|
|
|
String lastStorageHost = sharedPrefs.getString(PREF_BACKOFF_STORAGE_HOST, null);
|
|
final boolean storageHostIsUnchanged = lastStorageHost != null &&
|
|
lastStorageHost.equalsIgnoreCase(storageHostname);
|
|
if (storageHostIsUnchanged) {
|
|
Logger.debug(LOG_TAG, "Storage host is unchanged.");
|
|
if (!shouldPerformSync(storageBackoffHandler, "storage", extras)) {
|
|
Logger.info(LOG_TAG, "Not syncing: storage server requested backoff.");
|
|
callback.handleAborted(null, "Storage backoff");
|
|
return;
|
|
}
|
|
} else {
|
|
Logger.debug(LOG_TAG, "Received new storage host.");
|
|
}
|
|
|
|
// Invalidate the previous backoff, because our storage host has changed,
|
|
// or we never had one at all, or we're OK to sync.
|
|
storageBackoffHandler.setEarliestNextRequest(0L);
|
|
|
|
FxAccountGlobalSession globalSession = null;
|
|
try {
|
|
ClientsDataDelegate clientsDataDelegate = new SharedPreferencesClientsDataDelegate(sharedPrefs);
|
|
|
|
// We compute skew over time using SkewHandler. This yields an unchanging
|
|
// skew adjustment that the HawkAuthHeaderProvider uses to adjust its
|
|
// timestamps. Eventually we might want this to adapt within the scope of a
|
|
// global session.
|
|
final SkewHandler tokenServerSkewHandler = SkewHandler.getSkewHandlerForHostname(storageHostname);
|
|
final long tokenServerSkew = tokenServerSkewHandler.getSkewInSeconds();
|
|
final AuthHeaderProvider authHeaderProvider = new HawkAuthHeaderProvider(token.id, token.key.getBytes("UTF-8"), false, tokenServerSkew);
|
|
|
|
final Context context = getContext();
|
|
final SyncConfiguration syncConfig = new SyncConfiguration(token.uid, authHeaderProvider, sharedPrefs, syncKeyBundle);
|
|
|
|
globalSession = new FxAccountGlobalSession(token.endpoint, syncConfig, callback, context, extras, clientsDataDelegate);
|
|
globalSession.start();
|
|
} catch (Exception e) {
|
|
callback.handleError(globalSession, e);
|
|
return;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void handleFailure(TokenServerException e) {
|
|
debugAssertion(audience, assertion);
|
|
handleError(e);
|
|
}
|
|
|
|
@Override
|
|
public void handleError(Exception e) {
|
|
Logger.error(LOG_TAG, "Failed to get token.", e);
|
|
callback.handleError(null, e);
|
|
}
|
|
|
|
@Override
|
|
public void handleBackoff(int backoffSeconds) {
|
|
// This is the token server telling us to back off.
|
|
Logger.info(LOG_TAG, "Token server requesting backoff of " + backoffSeconds + "s. Backoff handler: " + tokenBackoffHandler);
|
|
didReceiveBackoff = true;
|
|
|
|
// If we've already stored a backoff, overrule it: we only use the server
|
|
// value for token server scheduling.
|
|
tokenBackoffHandler.setEarliestNextRequest(delay(backoffSeconds * 1000));
|
|
}
|
|
|
|
private long delay(long delay) {
|
|
return System.currentTimeMillis() + delay;
|
|
}
|
|
};
|
|
|
|
TokenServerClient tokenServerclient = new TokenServerClient(tokenServerEndpointURI, executor);
|
|
tokenServerclient.getTokenFromBrowserIDAssertion(assertion, true, clientState, delegate);
|
|
}
|
|
|
|
/**
|
|
* A trivial Sync implementation that does not cache client keys,
|
|
* certificates, or tokens.
|
|
*
|
|
* This should be replaced with a full {@link FxAccountAuthenticator}-based
|
|
* token implementation.
|
|
*/
|
|
@Override
|
|
public void onPerformSync(final Account account, final Bundle extras, final String authority, ContentProviderClient provider, final SyncResult syncResult) {
|
|
Logger.setThreadLogTag(FxAccountConstants.GLOBAL_LOG_TAG);
|
|
Logger.resetLogging();
|
|
|
|
if (this.lastSyncRealtimeMillis > 0L &&
|
|
(this.lastSyncRealtimeMillis + MINIMUM_SYNC_DELAY_MILLIS) > SystemClock.elapsedRealtime()) {
|
|
Logger.info(LOG_TAG, "Not syncing FxAccount " + Utils.obfuscateEmail(account.name) +
|
|
": minimum interval not met.");
|
|
return;
|
|
}
|
|
|
|
Logger.info(LOG_TAG, "Syncing FxAccount" +
|
|
" account named like " + Utils.obfuscateEmail(account.name) +
|
|
" for authority " + authority +
|
|
" with instance " + this + ".");
|
|
|
|
final Context context = getContext();
|
|
final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
|
|
if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
|
|
fxAccount.dump();
|
|
}
|
|
|
|
final CountDownLatch latch = new CountDownLatch(1);
|
|
final SyncDelegate syncDelegate = new SyncDelegate(context, latch, syncResult, fxAccount, notificationManager);
|
|
|
|
try {
|
|
final State state;
|
|
try {
|
|
state = fxAccount.getState();
|
|
} catch (Exception e) {
|
|
syncDelegate.handleError(e);
|
|
return;
|
|
}
|
|
|
|
// This will be the same chunk of SharedPreferences that we pass through to GlobalSession/SyncConfiguration.
|
|
final SharedPreferences sharedPrefs = fxAccount.getSyncPrefs();
|
|
|
|
// Check for a backoff right here.
|
|
final BackoffHandler schedulerBackoffHandler = new PrefsBackoffHandler(sharedPrefs, "scheduler");
|
|
if (!shouldPerformSync(schedulerBackoffHandler, "scheduler", extras)) {
|
|
Logger.info(LOG_TAG, "Not syncing (scheduler).");
|
|
syncDelegate.postponeSync(schedulerBackoffHandler.delayMilliseconds());
|
|
return;
|
|
}
|
|
|
|
final SchedulePolicy schedulePolicy = new FxAccountSchedulePolicy(context, fxAccount);
|
|
|
|
// Set a small scheduled 'backoff' to rate-limit the next sync.
|
|
schedulePolicy.configureBackoffMillisBeforeSyncing(schedulerBackoffHandler);
|
|
|
|
final String audience = fxAccount.getAudience();
|
|
final String authServerEndpoint = fxAccount.getAccountServerURI();
|
|
final String tokenServerEndpoint = fxAccount.getTokenServerURI();
|
|
final URI tokenServerEndpointURI = new URI(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() {
|
|
@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 RSACryptoImplementation.generateKeyPair(1024);
|
|
}
|
|
|
|
@Override
|
|
public void handleTransition(Transition transition, State state) {
|
|
Logger.info(LOG_TAG, "handleTransition: " + transition + " to " + state.getStateLabel());
|
|
}
|
|
|
|
private boolean shouldRequestToken(final BackoffHandler tokenBackoffHandler, final Bundle extras) {
|
|
return shouldPerformSync(tokenBackoffHandler, "token", extras);
|
|
}
|
|
|
|
@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;
|
|
}
|
|
|
|
final Married married = (Married) state;
|
|
SkewHandler skewHandler = SkewHandler.getSkewHandlerFromEndpointString(tokenServerEndpoint);
|
|
final long now = System.currentTimeMillis();
|
|
final long issuedAtMillis = now + skewHandler.getSkewInMillis();
|
|
final long assertionDurationMillis = this.getAssertionDurationInMilliseconds();
|
|
final String assertion = married.generateAssertion(audience, JSONWebTokenUtils.DEFAULT_ASSERTION_ISSUER, issuedAtMillis, assertionDurationMillis);
|
|
|
|
/*
|
|
* At this point we're in the correct state to sync, and we're ready to fetch
|
|
* a token and do some work.
|
|
*
|
|
* But first we need to do two things:
|
|
* 1. Check to see whether we're in a backoff situation for the token server.
|
|
* If we are, but we're not forcing a sync, then we go no further.
|
|
* 2. Clear an existing backoff (if we're syncing it doesn't matter, and if
|
|
* we're forcing we'll get a new backoff if things are still bad).
|
|
*
|
|
* Note that we don't check the storage backoff before the token dance: the token
|
|
* server tells us which server we're syncing to!
|
|
*
|
|
* That logic lives in the TokenServerClientDelegate elsewhere in this file.
|
|
*/
|
|
|
|
// Strictly speaking this backoff check could be done prior to walking through
|
|
// the login state machine, allowing us to short-circuit sooner.
|
|
// We don't expect many token server backoffs, and most users will be sitting
|
|
// in the Married state, so instead we simply do this here, once.
|
|
final BackoffHandler tokenBackoffHandler = new PrefsBackoffHandler(sharedPrefs, "token");
|
|
if (!shouldRequestToken(tokenBackoffHandler, extras)) {
|
|
Logger.info(LOG_TAG, "Not syncing (token server).");
|
|
syncDelegate.postponeSync(tokenBackoffHandler.delayMilliseconds());
|
|
return;
|
|
}
|
|
|
|
final SessionCallback sessionCallback = new SessionCallback(syncDelegate, schedulePolicy);
|
|
final KeyBundle syncKeyBundle = married.getSyncKeyBundle();
|
|
final String clientState = married.getClientState();
|
|
syncWithAssertion(audience, assertion, tokenServerEndpointURI, tokenBackoffHandler, sharedPrefs, syncKeyBundle, clientState, sessionCallback, extras);
|
|
} catch (Exception e) {
|
|
syncDelegate.handleError(e);
|
|
return;
|
|
}
|
|
}
|
|
});
|
|
|
|
latch.await();
|
|
} catch (Exception e) {
|
|
Logger.error(LOG_TAG, "Got error syncing.", e);
|
|
syncDelegate.handleError(e);
|
|
}
|
|
|
|
Logger.info(LOG_TAG, "Syncing done.");
|
|
lastSyncRealtimeMillis = SystemClock.elapsedRealtime();
|
|
}
|
|
|
|
protected void debugAssertion(String audience, String assertion) {
|
|
final CountDownLatch verifierLatch = new CountDownLatch(1);
|
|
BrowserIDRemoteVerifierClient client = new BrowserIDRemoteVerifierClient(URI.create(BrowserIDRemoteVerifierClient.DEFAULT_VERIFIER_URL));
|
|
client.verify(audience, assertion, new BrowserIDVerifierDelegate() {
|
|
@Override
|
|
public void handleSuccess(ExtendedJSONObject response) {
|
|
Logger.info(LOG_TAG, "Remote verifier returned success: " + response.toJSONString());
|
|
verifierLatch.countDown();
|
|
}
|
|
|
|
@Override
|
|
public void handleFailure(ExtendedJSONObject response) {
|
|
Logger.warn(LOG_TAG, "Remote verifier returned failure: " + response.toJSONString());
|
|
verifierLatch.countDown();
|
|
}
|
|
|
|
@Override
|
|
public void handleError(Exception e) {
|
|
Logger.error(LOG_TAG, "Remote verifier returned error.", e);
|
|
verifierLatch.countDown();
|
|
}
|
|
});
|
|
|
|
try {
|
|
verifierLatch.await();
|
|
} catch (InterruptedException e) {
|
|
Logger.error(LOG_TAG, "Got error.", e);
|
|
}
|
|
}
|
|
}
|