Bug 1435929 - refactor browserid_identity.js to be less confusing and error prone. r=eoger,tcsc

MozReview-Commit-ID: IJPQv4ZvJlp

--HG--
extra : rebase_source : d3052d120778dc1e8111ba76ee9df285d5483786
This commit is contained in:
Mark Hammond 2018-02-06 14:05:45 +11:00
Родитель 3eb90874c6
Коммит 6afd2c2ec1
18 изменённых файлов: 279 добавлений и 565 удалений

Просмотреть файл

@ -44,7 +44,6 @@ var initializeIdentityWithTokenServerResponse = function(response) {
// tie it all together.
Weave.Status.__authManager = Weave.Service.identity = new BrowserIDManager();
Weave.Service._clusterManager = Weave.Service.identity.createClusterManager(Weave.Service);
let browseridManager = Weave.Service.identity;
// a sanity check
if (!(browseridManager instanceof BrowserIDManager)) {

Просмотреть файл

@ -26,6 +26,7 @@ ChromeUtils.import("resource://services-common/utils.js");
ChromeUtils.import("resource://services-crypto/utils.js");
ChromeUtils.import("resource://services-sync/util.js");
ChromeUtils.import("resource://services-sync/browserid_identity.js");
ChromeUtils.import("resource://testing-common/Assert.jsm");
ChromeUtils.import("resource://testing-common/services/common/logging.js");
ChromeUtils.import("resource://testing-common/services/sync/fakeservices.js");
ChromeUtils.import("resource://gre/modules/FxAccounts.jsm");
@ -162,7 +163,7 @@ var makeFxAccountsInternalMock = function(config) {
return accountState;
},
_getAssertion(audience) {
return Promise.resolve("assertion");
return Promise.resolve(config.fxaccount.user.assertion);
},
};
};
@ -192,6 +193,8 @@ var configureFxAccountIdentity = function(authService,
let mockTSC = { // TokenServerClient
async getTokenFromBrowserIDAssertion(uri, assertion) {
Assert.equal(uri, Services.prefs.getStringPref("identity.sync.tokenserver.uri"));
Assert.equal(assertion, config.fxaccount.user.assertion);
config.fxaccount.token.uid = config.username;
return config.fxaccount.token;
},
@ -220,10 +223,12 @@ var configureIdentity = async function(identityOverrides, server) {
}
configureFxAccountIdentity(ns.Service.identity, config);
await ns.Service.identity.initializeWithCurrentIdentity();
// The token is fetched in the background, whenReadyToAuthenticate is resolved
// when we are ready.
await ns.Service.identity.whenReadyToAuthenticate.promise;
// because we didn't send any FxA LOGIN notifications we must set the username.
ns.Service.identity.username = config.username;
// many of these tests assume all the auth stuff is setup and don't hit
// a path which causes that auth to magically happen - so do it now.
await ns.Service.identity._ensureValidToken();
// and cheat to avoid requiring each test do an explicit login - give it
// a cluster URL.
if (config.fxaccount.token.endpoint) {

Просмотреть файл

@ -28,6 +28,7 @@ const TOPICS = [
"weave:service:sync:start",
"weave:service:sync:finish",
"weave:service:sync:error",
"weave:service:start-over:finish",
"fxaccounts:onverified",
"fxaccounts:onlogin", // Defined in FxAccountsCommon, pulling it is expensive.
"fxaccounts:onlogout",

Просмотреть файл

@ -9,7 +9,6 @@ var EXPORTED_SYMBOLS = ["BrowserIDManager", "AuthenticationError"];
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/Log.jsm");
ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm");
ChromeUtils.import("resource://gre/modules/FxAccounts.jsm");
ChromeUtils.import("resource://services-common/async.js");
ChromeUtils.import("resource://services-common/utils.js");
@ -43,6 +42,7 @@ ChromeUtils.import("resource://gre/modules/FxAccountsCommon.js", fxAccountsCommo
const OBSERVER_TOPICS = [
fxAccountsCommon.ONLOGIN_NOTIFICATION,
fxAccountsCommon.ONVERIFIED_NOTIFICATION,
fxAccountsCommon.ONLOGOUT_NOTIFICATION,
fxAccountsCommon.ON_ACCOUNT_STATE_CHANGE_NOTIFICATION,
];
@ -158,11 +158,14 @@ function BrowserIDManager() {
this._fxaService = fxAccounts;
this._tokenServerClient = new TokenServerClient();
this._tokenServerClient.observerPrefix = "weave:service";
// will be a promise that resolves when we are ready to authenticate
this.whenReadyToAuthenticate = null;
this._log = log;
XPCOMUtils.defineLazyPreferenceGetter(this, "_username", "services.sync.username");
}
this.asyncObserver = Async.asyncObserver(this, log);
for (let topic of OBSERVER_TOPICS) {
Services.obs.addObserver(this.asyncObserver, topic);
}
};
this.BrowserIDManager.prototype = {
_fxaService: null,
@ -171,15 +174,6 @@ this.BrowserIDManager.prototype = {
_token: null,
_signedInUser: null, // the signedinuser we got from FxAccounts.
// null if no error, otherwise a LOGIN_FAILED_* value that indicates why
// we failed to authenticate (but note it might not be an actual
// authentication problem, just a transient network error or similar)
_authFailureReason: null,
// it takes some time to fetch a sync key bundle, so until this flag is set,
// we don't consider the lack of a keybundle as a failure state.
_shouldHaveSyncKeyBundle: false,
hashedUID() {
if (!this._hashedUID) {
throw new Error("hashedUID: Don't seem to have previously seen a token");
@ -196,133 +190,21 @@ this.BrowserIDManager.prototype = {
return Utils.sha256(deviceID + uid);
},
deviceID() {
return this._signedInUser && this._signedInUser.deviceId;
},
initialize() {
for (let topic of OBSERVER_TOPICS) {
Services.obs.addObserver(this, topic);
}
},
/**
* Ensure the user is logged in. Returns a promise that resolves when
* the user is logged in, or is rejected if the login attempt has failed.
*/
ensureLoggedIn() {
if (!this._shouldHaveSyncKeyBundle && this.whenReadyToAuthenticate) {
// We are already in the process of logging in.
return this.whenReadyToAuthenticate.promise;
}
// If we are already happy then there is nothing more to do.
if (this._syncKeyBundle) {
return Promise.resolve();
}
// Similarly, if we have a previous failure that implies an explicit
// re-entering of credentials by the user is necessary we don't take any
// further action - an observer will fire when the user does that.
if (Weave.Status.login == LOGIN_FAILED_LOGIN_REJECTED) {
return Promise.reject(new Error("User needs to re-authenticate"));
}
// So - we've a previous auth problem and aren't currently attempting to
// log in - so fire that off.
this.initializeWithCurrentIdentity();
return this.whenReadyToAuthenticate.promise;
},
finalize() {
// After this is called, we can expect Service.identity != this.
for (let topic of OBSERVER_TOPICS) {
Services.obs.removeObserver(this, topic);
Services.obs.removeObserver(this.asyncObserver, topic);
}
this.resetCredentials();
this._signedInUser = null;
},
async initializeWithCurrentIdentity(isInitialSync = false) {
// While this function returns a promise that resolves once we've started
// the auth process, that process is complete when
// this.whenReadyToAuthenticate.promise resolves.
this._log.trace("initializeWithCurrentIdentity");
// Reset the world before we do anything async.
this.whenReadyToAuthenticate = PromiseUtils.defer();
this.whenReadyToAuthenticate.promise.catch(err => {
this._log.error("Could not authenticate", err);
});
// initializeWithCurrentIdentity() can be called after the
// identity module was first initialized, e.g., after the
// user completes a force authentication, so we should make
// sure all credentials are reset before proceeding.
this.resetCredentials();
this._authFailureReason = null;
try {
let accountData = await this._fxaService.getSignedInUser();
if (!accountData) {
this._log.info("initializeWithCurrentIdentity has no user logged in");
// and we are as ready as we can ever be for auth.
this._shouldHaveSyncKeyBundle = true;
this.whenReadyToAuthenticate.reject("no user is logged in");
return;
}
this.username = accountData.email;
this._updateSignedInUser(accountData);
// The user must be verified before we can do anything at all; we kick
// this and the rest of initialization off in the background (ie, we
// don't return the promise)
CommonUtils.nextTick(async () => {
try {
this._log.info("Waiting for user to be verified.");
if (!accountData.verified) {
telemetryHelper.maybeRecordLoginState(telemetryHelper.STATES.NOTVERIFIED);
}
accountData = await this._fxaService.whenVerified(accountData);
this._updateSignedInUser(accountData);
this._log.info("Starting fetch for key bundle.");
let token = await this._fetchTokenForUser();
this._token = token;
if (token) {
// We may not have a token if the master-password is locked - but we
// still treat this as "success" so we don't prompt for re-authentication.
this._hashedUID = token.hashed_fxa_uid; // see _ensureValidToken for why we do this...
}
this._shouldHaveSyncKeyBundle = true; // and we should actually have one...
this.whenReadyToAuthenticate.resolve();
this._log.info("Background fetch for key bundle done");
Weave.Status.login = LOGIN_SUCCEEDED;
if (isInitialSync) {
this._log.info("Doing initial sync actions");
Svc.Prefs.set("firstSync", "resetClient");
Services.obs.notifyObservers(null, "weave:service:setup-complete");
CommonUtils.nextTick(Weave.Service.sync, Weave.Service);
}
} catch (authErr) {
// report what failed...
this._log.error("Background fetch for key bundle failed", authErr);
this._shouldHaveSyncKeyBundle = true; // but we probably don't have one...
this.whenReadyToAuthenticate.reject(authErr);
}
// and we are done - the fetch continues on in the background...
});
} catch (err) {
this._log.error("Processing logged in account", err);
}
},
_updateSignedInUser(userData) {
// This object should only ever be used for a single user. It is an
// error to update the data if the user changes (but updates are still
// necessary, as each call may add more attributes to the user).
// We start with no user, so an initial update is always ok.
if (this._signedInUser && this._signedInUser.email != userData.email) {
if (this._signedInUser && this._signedInUser.uid != userData.uid) {
throw new Error("Attempting to update to a different user.");
}
this._signedInUser = userData;
@ -337,57 +219,62 @@ this.BrowserIDManager.prototype = {
this._token = null;
},
observe(subject, topic, data) {
async observe(subject, topic, data) {
this._log.debug("observed " + topic);
switch (topic) {
case fxAccountsCommon.ONLOGIN_NOTIFICATION: {
this._log.info("A user has logged in");
// If our existing Sync state is that we needed to reauth, clear that
// state now - it will get reset back if a problem persists.
if (Weave.Status.login == LOGIN_FAILED_LOGIN_REJECTED) {
Weave.Status.login = LOGIN_SUCCEEDED;
}
// This should only happen if we've been initialized without a current
// user - otherwise we'd have seen the LOGOUT notification and been
// thrown away.
// The exception is when we've initialized with a user that needs to
// reauth with the server - in that case we will also get here, but
// should have the same identity, and so we pass `false` into
// initializeWithCurrentIdentity so that we won't do a full sync for our
// first sync if we can avoid it.
// initializeWithCurrentIdentity will throw and log if these constraints
// aren't met (indirectly, via _updateSignedInUser()), so just go ahead
// and do the init.
let firstLogin = !this.username;
this.initializeWithCurrentIdentity(firstLogin);
this.resetCredentials();
let accountData = await this._fxaService.getSignedInUser();
this._updateSignedInUser(accountData);
if (!firstLogin) {
// We still want to trigger these even if it isn't our first login.
// Note that the promise returned by `initializeWithCurrentIdentity`
// is resolved at the start of authentication, but we don't want to fire
// this event or start the next sync until after authentication is done
// (which is signaled by `this.whenReadyToAuthenticate.promise` resolving).
this.whenReadyToAuthenticate.promise.then(() => {
Services.obs.notifyObservers(null, "weave:service:setup-complete");
return Async.promiseYield();
}).then(() => {
return Weave.Service.sync();
}).catch(e => {
this._log.warn("Failed to trigger setup complete notification", e);
});
if (!accountData.verified) {
// wait for a verified notification before we kick sync off.
this._log.info("The user is not verified");
break;
}
} break;
}
// We've been configured with an already verified user, so fall-through.
case fxAccountsCommon.ONVERIFIED_NOTIFICATION: {
this._log.info("The user became verified");
// Set the username now - that will cause Sync to know it is configured
let accountData = await this._fxaService.getSignedInUser();
this.username = accountData.email;
// And actually sync. If we've never synced before, we force a full sync.
// If we have, then we are probably just reauthenticating so it's a normal sync.
// We can use any pref that must be set if we've synced before.
let isFirstSync = !Svc.Prefs.get("client.syncID", null);
if (isFirstSync) {
this._log.info("Doing initial sync actions");
Svc.Prefs.set("firstSync", "resetClient");
}
Services.obs.notifyObservers(null, "weave:service:setup-complete");
// There's no need to wait for sync to complete and it would deadlock
// our AsyncObserver.
Weave.Service.sync({why: "login"});
break;
}
case fxAccountsCommon.ONLOGOUT_NOTIFICATION:
Async.promiseSpinningly(Weave.Service.startOver());
Weave.Service.startOver().then(() => {
this._log.trace("startOver completed");
}).catch(err => {
this._log.warn("Failed to reset sync", err);
});
// startOver will cause this instance to be thrown away, so there's
// nothing else to do.
break;
case fxAccountsCommon.ON_ACCOUNT_STATE_CHANGE_NOTIFICATION:
// throw away token and fetch a new one
// throw away token forcing us to fetch a new one later.
this.resetCredentials();
this._ensureValidToken().catch(err =>
this._log.error("Error while fetching a new token", err));
break;
}
},
@ -439,7 +326,7 @@ this.BrowserIDManager.prototype = {
* Resets/Drops all credentials we hold for the current user.
*/
resetCredentials() {
this.resetSyncKeyBundle();
this._syncKeyBundle = null;
this._token = null;
this._hashedUID = null;
// The cluster URL comes from the token, so resetting it to empty will
@ -447,14 +334,6 @@ this.BrowserIDManager.prototype = {
Weave.Service.clusterURL = null;
},
/**
* Resets/Drops the sync key bundle we hold for the current user.
*/
resetSyncKeyBundle() {
this._syncKeyBundle = null;
this._shouldHaveSyncKeyBundle = false;
},
/**
* Pre-fetches any information that might help with migration away from this
* identity. Called after every sync and is really just an optimization that
@ -465,18 +344,11 @@ this.BrowserIDManager.prototype = {
// nothing to do here until we decide to migrate away from FxA.
},
/**
* Return credentials hosts for this identity only.
*/
_getSyncCredentialsHosts() {
return Utils.getSyncCredentialsHostsFxA();
},
/**
* Deletes Sync credentials from the password manager.
*/
deleteSyncCredentials() {
for (let host of this._getSyncCredentialsHosts()) {
for (let host of Utils.getSyncCredentialsHosts()) {
let logins = Services.logins.findLogins({}, host, "", "");
for (let login of logins) {
Services.logins.removeLogin(login);
@ -484,31 +356,6 @@ this.BrowserIDManager.prototype = {
}
},
/**
* The current state of the auth credentials.
*
* This essentially validates that enough credentials are available to use
* Sync. It doesn't check we have all the keys we need as the master-password
* may have been locked when we tried to get them - we rely on
* unlockAndVerifyAuthState to check that for us.
*/
get currentAuthState() {
if (this._authFailureReason) {
this._log.info("currentAuthState returning " + this._authFailureReason +
" due to previous failure");
return this._authFailureReason;
}
// TODO: need to revisit this. Currently this isn't ready to go until
// both the username and syncKeyBundle are both configured and having no
// username seems to make things fail fast so that's good.
if (!this.username) {
return LOGIN_FAILED_NO_USERNAME;
}
return STATUS_OK;
},
/**
* Verify the current auth state, unlocking the master-password if necessary.
*
@ -516,8 +363,22 @@ this.BrowserIDManager.prototype = {
* attempting to unlock.
*/
async unlockAndVerifyAuthState() {
let data = await this._fxaService.getSignedInUser();
if (!data) {
log.debug("unlockAndVerifyAuthState has no user");
return LOGIN_FAILED_NO_USERNAME;
}
if (!data.verified) {
// Treat not verified as if the user needs to re-auth, so the browser
// UI reflects the state.
log.debug("unlockAndVerifyAuthState has an unverified user");
telemetryHelper.maybeRecordLoginState(telemetryHelper.STATES.NOTVERIFIED);
return LOGIN_FAILED_LOGIN_REJECTED;
}
this._updateSignedInUser(data);
if ((await this._fxaService.canGetKeys())) {
log.debug("unlockAndVerifyAuthState already has (or can fetch) sync keys");
telemetryHelper.maybeRecordLoginState(telemetryHelper.STATES.SUCCESS);
return STATUS_OK;
}
// so no keys - ensure MP unlocked.
@ -528,15 +389,16 @@ this.BrowserIDManager.prototype = {
}
// now we are unlocked we must re-fetch the user data as we may now have
// the details that were previously locked away.
const accountData = await this._fxaService.getSignedInUser();
this._updateSignedInUser(accountData);
this._updateSignedInUser(await this._fxaService.getSignedInUser());
// If we still can't get keys it probably means the user authenticated
// without unlocking the MP or cleared the saved logins, so we've now
// lost them - the user will need to reauth before continuing.
let result;
if ((await this._fxaService.canGetKeys())) {
telemetryHelper.maybeRecordLoginState(telemetryHelper.STATES.SUCCESS);
result = STATUS_OK;
} else {
telemetryHelper.maybeRecordLoginState(telemetryHelper.STATES.REJECTED);
result = LOGIN_FAILED_LOGIN_REJECTED;
}
log.debug("unlockAndVerifyAuthState re-fetched credentials and is returning", result);
@ -547,7 +409,7 @@ this.BrowserIDManager.prototype = {
* Do we have a non-null, not yet expired token for the user currently
* signed in?
*/
hasValidToken() {
_hasValidToken() {
// If pref is set to ignore cached authentication credentials for debugging,
// then return false to force the fetching of a new token.
if (IGNORE_CACHED_AUTH_CREDENTIALS) {
@ -578,144 +440,143 @@ this.BrowserIDManager.prototype = {
},
// Refresh the sync token for our user. Returns a promise that resolves
// with a token (which may be null in one sad edge-case), or rejects with an
// error.
// with a token, or rejects with an error.
async _fetchTokenForUser() {
// tokenServerURI is mis-named - convention is uri means nsISomething...
let tokenServerURI = this._tokenServerUrl;
let log = this._log;
let client = this._tokenServerClient;
let fxa = this._fxaService;
let userData = this._signedInUser;
// gotta be verified to fetch a token.
if (!this._signedInUser.verified) {
throw new Error("User is not verified");
}
// We need keys for things to work. If we don't have them, just
// return null for the token - sync calling unlockAndVerifyAuthState()
// before actually syncing will setup the error states if necessary.
if (!(await this._fxaService.canGetKeys())) {
log.info("Unable to fetch keys (master-password locked?), so aborting token fetch");
return Promise.resolve(null);
this._log.info("Unable to fetch keys (master-password locked?), so aborting token fetch");
throw new Error("Can't fetch a token as we can't get keys");
}
let maybeFetchKeys = () => {
log.info("Getting keys");
return this._fxaService.getKeys().then(
newUserData => {
userData = newUserData;
this._updateSignedInUser(userData); // throws if the user changed.
}
);
};
// Do the assertion/certificate/token dance, with a retry.
let getToken = async () => {
this._log.info("Getting an assertion from", this._tokenServerUrl);
const audience = Services.io.newURI(this._tokenServerUrl).prePath;
const assertion = await this._fxaService.getAssertion(audience);
let getToken = async (assertion) => {
log.debug("Getting a token");
let headers = {"X-Client-State": userData.kXCS};
// Exceptions will be handled by the caller.
const token = await client.getTokenFromBrowserIDAssertion(tokenServerURI, assertion, headers);
log.debug("Successfully got a sync token");
this._log.debug("Getting a token");
const headers = {"X-Client-State": this._signedInUser.kXCS};
const token = await this._tokenServerClient.getTokenFromBrowserIDAssertion(
this._tokenServerUrl, assertion, headers);
this._log.trace("Successfully got a token");
return token;
};
let getAssertion = () => {
log.info("Getting an assertion from", tokenServerURI);
let audience = Services.io.newURI(tokenServerURI).prePath;
return fxa.getAssertion(audience);
};
let token;
try {
try {
this._log.info("Getting keys");
this._updateSignedInUser(await this._fxaService.getKeys()); // throws if the user changed.
// wait until the account email is verified and we know that
// getAssertion() will return a real assertion (not null).
return fxa.whenVerified(this._signedInUser)
.then(() => maybeFetchKeys())
.then(() => getAssertion())
.then(assertion => getToken(assertion))
.catch(err => {
token = await getToken();
} catch (err) {
// If we get a 401 fetching the token it may be that our certificate
// needs to be regenerated.
if (!err.response || err.response.status !== 401) {
return Promise.reject(err);
}
log.warn("Token server returned 401, refreshing certificate and retrying token fetch");
return fxa.invalidateCertificate()
.then(() => getAssertion())
.then(assertion => getToken(assertion));
})
.then(token => {
// TODO: Make it be only 80% of the duration, so refresh the token
// before it actually expires. This is to avoid sync storage errors
// otherwise, we get a nasty notification bar briefly. Bug 966568.
token.expiration = this._now() + (token.duration * 1000) * 0.80;
if (!this._syncKeyBundle) {
this._syncKeyBundle = BulkKeyBundle.fromHexKey(userData.kSync);
}
telemetryHelper.maybeRecordLoginState(telemetryHelper.STATES.SUCCESS);
return token;
})
.catch(err => {
// TODO: unify these errors - we need to handle errors thrown by
// both tokenserverclient and hawkclient.
// A tokenserver error thrown based on a bad response.
if (err.response && err.response.status === 401) {
err = new AuthenticationError(err, "tokenserver");
// A hawkclient error.
} else if (err.code && err.code === 401) {
err = new AuthenticationError(err, "hawkclient");
// An FxAccounts.jsm error.
} else if (err.message == fxAccountsCommon.ERROR_AUTH_ERROR) {
err = new AuthenticationError(err, "fxaccounts");
throw err;
}
this._log.warn("Token server returned 401, refreshing certificate and retrying token fetch");
await this._fxaService.invalidateCertificate();
token = await getToken();
}
// TODO: Make it be only 80% of the duration, so refresh the token
// before it actually expires. This is to avoid sync storage errors
// otherwise, we may briefly enter a "needs reauthentication" state.
// (XXX - the above may no longer be true - someone should check ;)
token.expiration = this._now() + (token.duration * 1000) * 0.80;
if (!this._syncKeyBundle) {
this._syncKeyBundle = BulkKeyBundle.fromHexKey(this._signedInUser.kSync);
}
telemetryHelper.maybeRecordLoginState(telemetryHelper.STATES.SUCCESS);
Weave.Status.login = LOGIN_SUCCEEDED;
this._token = token;
return token;
} catch (caughtErr) {
let err = caughtErr; // The error we will rethrow.
// TODO: unify these errors - we need to handle errors thrown by
// both tokenserverclient and hawkclient.
// A tokenserver error thrown based on a bad response.
if (err.response && err.response.status === 401) {
err = new AuthenticationError(err, "tokenserver");
// A hawkclient error.
} else if (err.code && err.code === 401) {
err = new AuthenticationError(err, "hawkclient");
// An FxAccounts.jsm error.
} else if (err.message == fxAccountsCommon.ERROR_AUTH_ERROR) {
err = new AuthenticationError(err, "fxaccounts");
}
// TODO: write tests to make sure that different auth error cases are handled here
// properly: auth error getting assertion, auth error getting token (invalid generation
// and client-state error)
if (err instanceof AuthenticationError) {
this._log.error("Authentication error in _fetchTokenForUser", err);
// set it to the "fatal" LOGIN_FAILED_LOGIN_REJECTED reason.
this._authFailureReason = LOGIN_FAILED_LOGIN_REJECTED;
telemetryHelper.maybeRecordLoginState(telemetryHelper.STATES.REJECTED);
} else {
this._log.error("Non-authentication error in _fetchTokenForUser", err);
// for now assume it is just a transient network related problem
// (although sadly, it might also be a regular unhandled exception)
this._authFailureReason = LOGIN_FAILED_NETWORK_ERROR;
}
// this._authFailureReason being set to be non-null in the above if clause
// ensures we are in the correct currentAuthState, and
// this._shouldHaveSyncKeyBundle being true ensures everything that cares knows
// that there is no authentication dance still under way.
this._shouldHaveSyncKeyBundle = true;
Weave.Status.login = this._authFailureReason;
throw err;
});
// TODO: write tests to make sure that different auth error cases are handled here
// properly: auth error getting assertion, auth error getting token (invalid generation
// and client-state error)
if (err instanceof AuthenticationError) {
this._log.error("Authentication error in _fetchTokenForUser", err);
// set it to the "fatal" LOGIN_FAILED_LOGIN_REJECTED reason.
Weave.Status.login = LOGIN_FAILED_LOGIN_REJECTED;
telemetryHelper.maybeRecordLoginState(telemetryHelper.STATES.REJECTED);
} else {
this._log.error("Non-authentication error in _fetchTokenForUser", err);
// for now assume it is just a transient network related problem
// (although sadly, it might also be a regular unhandled exception)
Weave.Status.login = LOGIN_FAILED_NETWORK_ERROR;
}
throw err;
}
},
// Returns a promise that is resolved when we have a valid token for the
// current user stored in this._token. When resolved, this._token is valid.
_ensureValidToken() {
if (this.hasValidToken()) {
this._log.debug("_ensureValidToken already has one");
return Promise.resolve();
// Returns a promise that is resolved with a valid token for the current
// user, or rejects if one can't be obtained.
// NOTE: This does all the authentication for Sync - it both sets the
// key bundle (ie, decryption keys) and does the token fetch. These 2
// concepts could be decoupled, but there doesn't seem any value in that
// currently.
async _ensureValidToken(forceNewToken = false) {
if (!this._signedInUser) {
throw new Error("no user is logged in");
}
const notifyStateChanged =
() => Services.obs.notifyObservers(null, "weave:service:login:change");
if (!this._signedInUser.verified) {
throw new Error("user is not verified");
}
await this.asyncObserver.promiseObserversComplete();
if (!forceNewToken && this._hasValidToken()) {
this._log.trace("_ensureValidToken already has one");
return this._token;
}
// We are going to grab a new token - re-use the same promise if we are
// already fetching one.
if (!this._ensureValidTokenPromise) {
this._ensureValidTokenPromise = this.__ensureValidToken().finally(() => {
this._ensureValidTokenPromise = null;
});
}
return this._ensureValidTokenPromise;
},
async __ensureValidToken() {
// reset this._token as a safety net to reduce the possibility of us
// repeatedly attempting to use an invalid token if _fetchTokenForUser throws.
this._token = null;
return this._fetchTokenForUser().then(
token => {
this._token = token;
// we store the hashed UID from the token so that if we see a transient
// error fetching a new token we still know the "most recent" hashed
// UID for telemetry.
if (token) {
// We may not have a token if the master-password is locked.
this._hashedUID = token.hashed_fxa_uid;
}
notifyStateChanged();
},
error => {
notifyStateChanged();
throw error;
}
);
try {
let token = await this._fetchTokenForUser();
this._token = token;
// we store the hashed UID from the token so that if we see a transient
// error fetching a new token we still know the "most recent" hashed
// UID for telemetry.
this._hashedUID = token.hashed_fxa_uid;
return token;
} finally {
Services.obs.notifyObservers(null, "weave:service:login:change");
}
},
getResourceAuthenticator() {
@ -758,36 +619,10 @@ this.BrowserIDManager.prototype = {
return {headers: {authorization: headerValue.field}};
},
createClusterManager(service) {
return new BrowserIDClusterManager(service);
},
// Tell Sync what the login status should be if it saw a 401 fetching
// info/collections as part of login verification (typically immediately
// after login.)
// In our case, it almost certainly means a transient error fetching a token
// (and hitting this will cause us to logout, which will correctly handle an
// authoritative login issue.)
loginStatusFromVerification404() {
return LOGIN_FAILED_NETWORK_ERROR;
},
};
/* An implementation of the ClusterManager for this identity
*/
function BrowserIDClusterManager(service) {
this._log = log;
this.service = service;
}
BrowserIDClusterManager.prototype = {
get identity() {
return this.service.identity;
},
/**
* Determine the cluster for the current user and update state.
* Returns true if a new cluster URL was found and it is different from
* the existing cluster URL, false otherwise.
*/
async setCluster() {
// Make sure we didn't get some unexpected response for the cluster.
@ -801,12 +636,12 @@ BrowserIDClusterManager.prototype = {
// resource.js returns to a plain-old string.
cluster = cluster.toString();
// Don't update stuff if we already have the right cluster
if (cluster == this.service.clusterURL) {
if (cluster == Weave.Service.clusterURL) {
return false;
}
this._log.debug("Setting cluster to " + cluster);
this.service.clusterURL = cluster;
Weave.Service.clusterURL = cluster;
return true;
},
@ -814,39 +649,27 @@ BrowserIDClusterManager.prototype = {
async _findCluster() {
try {
// Ensure we are ready to authenticate and have a valid token.
await this.identity.whenReadyToAuthenticate.promise;
// We need to handle node reassignment here. If we are being asked
// for a clusterURL while the service already has a clusterURL, then
// it's likely a 401 was received using the existing token - in which
// case we just discard the existing token and fetch a new one.
if (this.service.clusterURL) {
log.debug("_findCluster has a pre-existing clusterURL, so discarding the current token");
this.identity._token = null;
let forceNewToken = false;
if (Weave.Service.clusterURL) {
this._log.debug("_findCluster has a pre-existing clusterURL, so fetching a new token token");
forceNewToken = true;
}
await this.identity._ensureValidToken();
// The only reason (in theory ;) that we can end up with a null token
// is when this._fxaService.canGetKeys() returned false. In turn, this
// should only happen if the master-password is locked or the credentials
// storage is screwed, and in those cases we shouldn't have started
// syncing so shouldn't get here anyway.
// But better safe than sorry! To keep things clearer, throw an explicit
// exception - the message will appear in the logs and the error will be
// treated as transient.
if (!this.identity._token) {
throw new Error("Can't get a cluster URL as we can't fetch keys.");
}
let endpoint = this.identity._token.endpoint;
let token = await this._ensureValidToken(forceNewToken);
let endpoint = token.endpoint;
// For Sync 1.5 storage endpoints, we use the base endpoint verbatim.
// However, it should end in "/" because we will extend it with
// well known path components. So we add a "/" if it's missing.
if (!endpoint.endsWith("/")) {
endpoint += "/";
}
log.debug("_findCluster returning " + endpoint);
this._log.debug("_findCluster returning " + endpoint);
return endpoint;
} catch (err) {
log.info("Failed to fetch the cluster URL", err);
this._log.info("Failed to fetch the cluster URL", err);
// service.js's verifyLogin() method will attempt to fetch a cluster
// URL when it sees a 401. If it gets null, it treats it as a "real"
// auth error and sets Status.login to LOGIN_FAILED_LOGIN_REJECTED, which
@ -865,13 +688,4 @@ BrowserIDClusterManager.prototype = {
throw err;
}
},
getUserBaseURL() {
// Legacy Sync and FxA Sync construct the userBaseURL differently. Legacy
// Sync appends path components onto an empty path, and in FxA Sync the
// token server constructs this for us in an opaque manner. Since the
// cluster manager already sets the clusterURL on Service and also has
// access to the current identity, we added this functionality here.
return this.service.clusterURL;
}
};

Просмотреть файл

@ -19,12 +19,6 @@ SYNC_API_VERSION: "1.5",
STORAGE_VERSION: 5,
PREFS_BRANCH: "services.sync.",
// Host "key" to access Weave Identity in the password manager
PWDMGR_HOST: "chrome://weave",
PWDMGR_PASSWORD_REALM: "Mozilla Services Password",
PWDMGR_PASSPHRASE_REALM: "Mozilla Services Encryption Passphrase",
PWDMGR_KEYBUNDLE_REALM: "Mozilla Services Key Bundles",
// Put in [] because those aren't allowed in a collection name.
DEFAULT_KEYBUNDLE_NAME: "[default]",

Просмотреть файл

@ -86,7 +86,7 @@ Sync11Service.prototype = {
storageURL: null,
metaURL: null,
cryptoKeyURL: null,
// The cluster URL comes via the ClusterManager object, which in the FxA
// The cluster URL comes via the identity object, which in the FxA
// world is ebbedded in the token returned from the token server.
_clusterURL: null,
@ -129,10 +129,8 @@ Sync11Service.prototype = {
},
get userBaseURL() {
if (!this._clusterManager) {
return null;
}
return this._clusterManager.getUserBaseURL();
// The user URL is the cluster URL.
return this.clusterURL;
},
_updateCachedURLs: function _updateCachedURLs() {
@ -297,7 +295,6 @@ Sync11Service.prototype = {
this._log.info("Loading Weave " + WEAVE_VERSION);
this._clusterManager = this.identity.createClusterManager(this);
this.recordManager = new RecordManager(this);
this.enabled = true;
@ -656,22 +653,15 @@ Sync11Service.prototype = {
},
async verifyLogin(allow40XRecovery = true) {
if (!this.identity.username) {
this._log.warn("No username in verifyLogin.");
this.status.login = LOGIN_FAILED_NO_USERNAME;
return false;
}
// Attaching auth credentials to a request requires access to
// passwords, which means that Resource.get can throw MP-related
// exceptions!
// So we ask the identity to verify the login state after unlocking the
// master password (ie, this call is expected to prompt for MP unlock
// if necessary) while we still have control.
let unlockedState = await this.identity.unlockAndVerifyAuthState();
this._log.debug("Fetching unlocked auth state returned " + unlockedState);
if (unlockedState != STATUS_OK) {
this.status.login = unlockedState;
this.status.login = await this.identity.unlockAndVerifyAuthState();
this._log.debug("Fetching unlocked auth state returned " + this.status.login);
if (this.status.login != STATUS_OK) {
return false;
}
@ -679,7 +669,7 @@ Sync11Service.prototype = {
// Make sure we have a cluster to verify against.
// This is a little weird, if we don't get a node we pretend
// to succeed, since that probably means we just don't have storage.
if (this.clusterURL == "" && !(await this._clusterManager.setCluster())) {
if (this.clusterURL == "" && !(await this.identity.setCluster())) {
this.status.sync = NO_SYNC_NODE_FOUND;
return true;
}
@ -718,16 +708,13 @@ Sync11Service.prototype = {
case 404:
// Check that we're verifying with the correct cluster
if (allow40XRecovery && (await this._clusterManager.setCluster())) {
if (allow40XRecovery && (await this.identity.setCluster())) {
return await this.verifyLogin(false);
}
// We must have the right cluster, but the server doesn't expect us.
// The implications of this depend on the identity being used - for
// the legacy identity, it's an authoritatively "incorrect password",
// (ie, LOGIN_FAILED_LOGIN_REJECTED) but for FxA it probably means
// "transient error fetching auth token".
this.status.login = this.identity.loginStatusFromVerification404();
// For FxA this almost certainly means "transient error fetching token".
this.status.login = LOGIN_FAILED_NETWORK_ERROR;
return false;
default:
@ -826,8 +813,8 @@ Sync11Service.prototype = {
// possible, so let's fake for the CLIENT_NOT_CONFIGURED status for now
// by emptying the passphrase (we still need the password).
this._log.info("Service.startOver dropping sync key and logging out.");
this.identity.resetSyncKeyBundle();
this.status.login = LOGIN_FAILED_NO_PASSPHRASE;
this.identity.resetCredentials();
this.status.login = LOGIN_FAILED_NO_USERNAME;
this.logout();
Svc.Obs.notify("weave:service:start-over");
@ -850,7 +837,6 @@ Sync11Service.prototype = {
this.identity.finalize();
this.status.__authManager = null;
this.identity = Status._authManager;
this._clusterManager = this.identity.createClusterManager(this);
Svc.Obs.notify("weave:service:start-over:finish");
} catch (err) {
this._log.error("startOver failed to re-initialize the identity manager", err);
@ -868,22 +854,14 @@ Sync11Service.prototype = {
throw new Error("Application is offline, login should not be called");
}
this._log.info("Logging in the user.");
// Just let any errors bubble up - they've more context than we do!
try {
await this.identity.ensureLoggedIn();
} finally {
this._checkSetup(); // _checkSetup has a side effect of setting the right state.
}
this._updateCachedURLs();
this._log.info("User logged in successfully - verifying login.");
if (!(await this.verifyLogin())) {
// verifyLogin sets the failure states here.
throw new Error(`Login failed: ${this.status.login}`);
}
this._updateCachedURLs();
this._loggedIn = true;
return true;

Просмотреть файл

@ -49,7 +49,7 @@ EngineSynchronizer.prototype = {
}
// If we don't have a node, get one. If that fails, retry in 10 minutes.
if (!this.service.clusterURL && !(await this.service._clusterManager.setCluster())) {
if (!this.service.clusterURL && !(await this.service.identity.setCluster())) {
this.service.status.sync = NO_SYNC_NODE_FOUND;
this._log.info("No cluster URL found. Cannot sync.");
return;

Просмотреть файл

@ -19,7 +19,6 @@ var Status = {
return this.__authManager;
}
this.__authManager = new BrowserIDManager();
this.__authManager.initialize();
return this.__authManager;
},
@ -92,13 +91,12 @@ var Status = {
},
checkSetup: function checkSetup() {
let result = this._authManager.currentAuthState;
if (result == STATUS_OK) {
Status.service = result;
return result;
if (!this._authManager.username) {
Status.login = LOGIN_FAILED_NO_USERNAME;
Status.service = CLIENT_NOT_CONFIGURED;
} else if (Status.login == STATUS_OK) {
Status.service = STATUS_OK;
}
Status.login = result;
return Status.service;
},

Просмотреть файл

@ -609,25 +609,6 @@ var Utils = {
* reset when we drop sync credentials, etc.
*/
getSyncCredentialsHosts() {
let result = new Set(this.getSyncCredentialsHostsLegacy());
for (let host of this.getSyncCredentialsHostsFxA()) {
result.add(host);
}
return result;
},
/*
* Get the "legacy" identity hosts.
*/
getSyncCredentialsHostsLegacy() {
// the legacy sync host
return new Set([PWDMGR_HOST]);
},
/*
* Get the FxA identity hosts.
*/
getSyncCredentialsHostsFxA() {
let result = new Set();
// the FxA host
result.add(FxAccountsCommon.FXA_PWDMGR_HOST);

Просмотреть файл

@ -64,23 +64,22 @@ function MockFxAccounts() {
add_test(function test_initial_state() {
_("Verify initial state");
Assert.ok(!globalBrowseridManager._token);
Assert.ok(!globalBrowseridManager.hasValidToken());
Assert.ok(!globalBrowseridManager._hasValidToken());
run_next_test();
}
);
add_task(async function test_initialializeWithCurrentIdentity() {
_("Verify start after initializeWithCurrentIdentity");
globalBrowseridManager.initializeWithCurrentIdentity();
await globalBrowseridManager.whenReadyToAuthenticate.promise;
add_task(async function test_initialialize() {
_("Verify start after fetching token");
await globalBrowseridManager._ensureValidToken();
Assert.ok(!!globalBrowseridManager._token);
Assert.ok(globalBrowseridManager.hasValidToken());
Assert.ok(globalBrowseridManager._hasValidToken());
Assert.deepEqual(getLoginTelemetryScalar(), {SUCCESS: 1});
}
);
add_task(async function test_initialializeWithAuthErrorAndDeletedAccount() {
_("Verify sync unpair after initializeWithCurrentIdentity with auth error + account deleted");
_("Verify sync state with auth error + account deleted");
var identityConfig = makeIdentityConfig();
var browseridManager = new BrowserIDManager();
@ -117,33 +116,16 @@ add_task(async function test_initialializeWithAuthErrorAndDeletedAccount() {
let mockFxAClient = new AuthErrorMockFxAClient();
browseridManager._fxaService.internal._fxAccountsClient = mockFxAClient;
await browseridManager.initializeWithCurrentIdentity();
await Assert.rejects(browseridManager.whenReadyToAuthenticate.promise,
"should reject due to an auth error");
await Assert.rejects(browseridManager._ensureValidToken(),
"should reject due to an auth error");
Assert.ok(signCertificateCalled);
Assert.ok(accountStatusCalled);
Assert.ok(!browseridManager._token);
Assert.ok(!browseridManager.hasValidToken());
Assert.ok(!browseridManager._hasValidToken());
Assert.deepEqual(getLoginTelemetryScalar(), {REJECTED: 1});
});
add_task(async function test_initialializeWithNoKeys() {
_("Verify start after initializeWithCurrentIdentity without kSync, kXCS, kExtSync, kExtKbHash or keyFetchToken");
let identityConfig = makeIdentityConfig();
delete identityConfig.fxaccount.user.kSync;
delete identityConfig.fxaccount.user.kXCS;
delete identityConfig.fxaccount.user.kExtSync;
delete identityConfig.fxaccount.user.kExtKbHash;
// there's no keyFetchToken by default, so the initialize should fail.
configureFxAccountIdentity(globalBrowseridManager, identityConfig);
await globalBrowseridManager.initializeWithCurrentIdentity();
await globalBrowseridManager.whenReadyToAuthenticate.promise;
Assert.equal(Status.login, LOGIN_SUCCEEDED, "login succeeded even without keys");
Assert.equal(globalBrowseridManager._token, null, "we don't have a token");
});
add_task(async function test_getResourceAuthenticator() {
_("BrowserIDManager supplies a Resource Authenticator callback which returns a Hawk header.");
configureFxAccountIdentity(globalBrowseridManager);
@ -266,34 +248,19 @@ add_task(async function test_RESTResourceAuthenticatorSkew() {
add_task(async function test_ensureLoggedIn() {
configureFxAccountIdentity(globalBrowseridManager);
await globalBrowseridManager.initializeWithCurrentIdentity();
await globalBrowseridManager.whenReadyToAuthenticate.promise;
await globalBrowseridManager._ensureValidToken();
Assert.equal(Status.login, LOGIN_SUCCEEDED, "original initialize worked");
await globalBrowseridManager.ensureLoggedIn();
Assert.equal(Status.login, LOGIN_SUCCEEDED, "original ensureLoggedIn worked");
Assert.ok(globalBrowseridManager._shouldHaveSyncKeyBundle,
"_shouldHaveSyncKeyBundle should always be true after ensureLogin completes.");
Assert.ok(globalBrowseridManager._token);
// arrange for no logged in user.
let fxa = globalBrowseridManager._fxaService;
let signedInUser = fxa.internal.currentAccountState.storageManager.accountData;
fxa.internal.currentAccountState.storageManager.accountData = null;
globalBrowseridManager.initializeWithCurrentIdentity();
Assert.ok(!globalBrowseridManager._shouldHaveSyncKeyBundle,
"_shouldHaveSyncKeyBundle should be false so we know we are testing what we think we are.");
Status.login = LOGIN_FAILED_NO_USERNAME;
await Assert.rejects(globalBrowseridManager.ensureLoggedIn(), "expecting rejection due to no user");
Assert.ok(globalBrowseridManager._shouldHaveSyncKeyBundle,
"_shouldHaveSyncKeyBundle should always be true after ensureLogin completes.");
await Assert.rejects(globalBrowseridManager._ensureValidToken(true), "expecting rejection due to no user");
// Restore the logged in user to what it was.
fxa.internal.currentAccountState.storageManager.accountData = signedInUser;
Status.login = LOGIN_FAILED_LOGIN_REJECTED;
await Assert.rejects(globalBrowseridManager.ensureLoggedIn(),
"LOGIN_FAILED_LOGIN_REJECTED should have caused immediate rejection");
Assert.equal(Status.login, LOGIN_FAILED_LOGIN_REJECTED,
"status should remain LOGIN_FAILED_LOGIN_REJECTED");
Status.login = LOGIN_FAILED_NETWORK_ERROR;
await globalBrowseridManager.ensureLoggedIn();
await globalBrowseridManager._ensureValidToken();
Assert.equal(Status.login, LOGIN_SUCCEEDED, "final ensureLoggedIn worked");
});
@ -319,7 +286,7 @@ add_task(async function test_tokenExpiration() {
});
Assert.ok(bimExp._token.expiration < bimExp._now());
_("... means BrowserIDManager knows to re-fetch it on the next call.");
Assert.ok(!bimExp.hasValidToken());
Assert.ok(!bimExp._hasValidToken());
}
);
@ -334,8 +301,7 @@ add_task(async function test_getTokenErrors() {
});
let browseridManager = Service.identity;
await browseridManager.initializeWithCurrentIdentity();
await Assert.rejects(browseridManager.whenReadyToAuthenticate.promise,
await Assert.rejects(browseridManager._ensureValidToken(),
"should reject due to 401");
Assert.equal(Status.login, LOGIN_FAILED_LOGIN_REJECTED, "login was rejected");
@ -350,8 +316,7 @@ add_task(async function test_getTokenErrors() {
body: "",
});
browseridManager = Service.identity;
await browseridManager.initializeWithCurrentIdentity();
await Assert.rejects(browseridManager.whenReadyToAuthenticate.promise,
await Assert.rejects(browseridManager._ensureValidToken(),
"should reject due to non-JSON response");
Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login state is LOGIN_FAILED_NETWORK_ERROR");
});
@ -409,14 +374,13 @@ add_task(async function test_refreshCertificateOn401() {
browseridManager._tokenServerClient = mockTSC;
await browseridManager.initializeWithCurrentIdentity();
await browseridManager.whenReadyToAuthenticate.promise;
await browseridManager._ensureValidToken();
Assert.equal(getCertCount, 2);
Assert.ok(didReturn401);
Assert.ok(didReturn200);
Assert.ok(browseridManager._token);
Assert.ok(browseridManager.hasValidToken());
Assert.ok(browseridManager._hasValidToken());
});
@ -436,8 +400,7 @@ add_task(async function test_getTokenErrorWithRetry() {
});
let browseridManager = Service.identity;
await browseridManager.initializeWithCurrentIdentity();
await Assert.rejects(browseridManager.whenReadyToAuthenticate.promise,
await Assert.rejects(browseridManager._ensureValidToken(),
"should reject due to 503");
// The observer should have fired - check it got the value in the response.
@ -455,8 +418,7 @@ add_task(async function test_getTokenErrorWithRetry() {
});
browseridManager = Service.identity;
await browseridManager.initializeWithCurrentIdentity();
await Assert.rejects(browseridManager.whenReadyToAuthenticate.promise,
await Assert.rejects(browseridManager._ensureValidToken(),
"should reject due to no token in response");
// The observer should have fired - check it got the value in the response.
@ -490,7 +452,7 @@ add_task(async function test_getKeysErrorWithBackoff() {
});
let browseridManager = Service.identity;
await Assert.rejects(browseridManager.whenReadyToAuthenticate.promise,
await Assert.rejects(browseridManager._ensureValidToken(),
"should reject due to 503");
// The observer should have fired - check it got the value in the response.
@ -526,7 +488,7 @@ add_task(async function test_getKeysErrorWithRetry() {
});
let browseridManager = Service.identity;
await Assert.rejects(browseridManager.whenReadyToAuthenticate.promise,
await Assert.rejects(browseridManager._ensureValidToken(),
"should reject due to 503");
// The observer should have fired - check it got the value in the response.
@ -658,16 +620,8 @@ add_task(async function test_getKeysMissing() {
browseridManager._fxaService = fxa;
await browseridManager.initializeWithCurrentIdentity();
let ex;
try {
await browseridManager.whenReadyToAuthenticate.promise;
} catch (e) {
ex = e;
}
Assert.equal(ex.message, "user data missing: kSync, kXCS, kExtSync, kExtKbHash");
await Assert.rejects(browseridManager._ensureValidToken(),
/user data missing: kSync, kXCS, kExtSync, kExtKbHash/);
});
add_task(async function test_signedInUserMissing() {
@ -701,6 +655,7 @@ add_task(async function test_signedInUserMissing() {
});
browseridManager._fxaService = fxa;
browseridManager._signedInUser = await fxa.getSignedInUser();
let status = await browseridManager.unlockAndVerifyAuthState();
Assert.equal(status, LOGIN_FAILED_LOGIN_REJECTED);
@ -774,9 +729,8 @@ async function initializeIdentityWithHAWKResponseFactory(config, cbGetResponse)
let fxa = new FxAccounts(internal);
globalBrowseridManager._fxaService = fxa;
globalBrowseridManager._signedInUser = null;
await globalBrowseridManager.initializeWithCurrentIdentity();
await Assert.rejects(globalBrowseridManager.whenReadyToAuthenticate.promise,
globalBrowseridManager._signedInUser = await fxa.getSignedInUser();
await Assert.rejects(globalBrowseridManager._ensureValidToken(true),
"expecting rejection due to hawk error");
}

Просмотреть файл

@ -365,7 +365,7 @@ add_task(async function test_login_syncAndReportErrors_non_network_error() {
// when calling syncAndReportErrors
let server = await EHTestsCommon.sync_httpd_setup();
await EHTestsCommon.setUp(server);
Service.identity.resetSyncKeyBundle();
Service.identity._syncKeyBundle = null;
let promiseObserved = promiseOneObserver("weave:ui:login:error");
@ -418,7 +418,7 @@ add_task(async function test_login_syncAndReportErrors_prolonged_non_network_err
// reported when calling syncAndReportErrors.
let server = await EHTestsCommon.sync_httpd_setup();
await EHTestsCommon.setUp(server);
Service.identity.resetSyncKeyBundle();
Service.identity._syncKeyBundle = null;
let promiseObserved = promiseOneObserver("weave:ui:login:error");
@ -543,7 +543,7 @@ add_task(async function test_login_prolonged_non_network_error() {
// Test prolonged, non-network errors are reported
let server = await EHTestsCommon.sync_httpd_setup();
await EHTestsCommon.setUp(server);
Service.identity.resetSyncKeyBundle();
Service.identity._syncKeyBundle = null;
let promiseObserved = promiseOneObserver("weave:ui:login:error");
@ -630,7 +630,7 @@ add_task(async function test_login_non_network_error() {
// Test non-network errors are reported
let server = await EHTestsCommon.sync_httpd_setup();
await EHTestsCommon.setUp(server);
Service.identity.resetSyncKeyBundle();
Service.identity._syncKeyBundle = null;
let promiseObserved = promiseOneObserver("weave:ui:login:error");

Просмотреть файл

@ -25,7 +25,6 @@ add_task(async function setup() {
// Setup the FxA identity manager and cluster manager.
Status.__authManager = Service.identity = new BrowserIDManager();
Service._clusterManager = Service.identity.createClusterManager(Service);
// None of the failures in this file should result in a UI error.
function onUIError() {
@ -56,8 +55,9 @@ function prepareServer(cbAfterTokenFetch) {
__proto__: SyncServerCallback,
onRequest(req, resp) {
let full = `${req.scheme}://${req.host}:${req.port}${req.path}`;
Assert.ok(full.startsWith(config.fxaccount.token.endpoint),
`request made to ${full}`);
let expected = config.fxaccount.token.endpoint;
Assert.ok(full.startsWith(expected),
`request made to ${full}, expected ${expected}`);
}
};
let server = new SyncServer(callback);
@ -81,6 +81,7 @@ function prepareServer(cbAfterTokenFetch) {
let token = config.fxaccount.token;
token.endpoint = server.baseURI + "1.1" + trailingZeros + "/johndoe";
token.uid = config.username;
_(`test server saw token fetch - endpoint now ${token.endpoint}`);
numTokenRequests += 1;
res(token);
if (cbAfterTokenFetch) {

Просмотреть файл

@ -15,11 +15,7 @@ add_task(async function test_findCluster() {
body: "",
});
await Service.identity.initializeWithCurrentIdentity();
await Assert.rejects(Service.identity.whenReadyToAuthenticate.promise,
"should reject due to 500");
await Assert.rejects(Service._clusterManager._findCluster());
await Assert.rejects(Service.identity._findCluster());
_("_findCluster() returns null on authentication errors.");
initializeIdentityWithTokenServerResponse({
@ -28,11 +24,7 @@ add_task(async function test_findCluster() {
body: "{}",
});
await Service.identity.initializeWithCurrentIdentity();
await Assert.rejects(Service.identity.whenReadyToAuthenticate.promise,
"should reject due to 401");
let cluster = await Service._clusterManager._findCluster();
let cluster = await Service.identity._findCluster();
Assert.strictEqual(cluster, null);
_("_findCluster() works with correct tokenserver response.");
@ -50,9 +42,7 @@ add_task(async function test_findCluster() {
})
});
await Service.identity.initializeWithCurrentIdentity();
await Service.identity.whenReadyToAuthenticate.promise;
cluster = await Service._clusterManager._findCluster();
cluster = await Service.identity._findCluster();
// The cluster manager ensures a trailing "/"
Assert.strictEqual(cluster, endpoint + "/");

Просмотреть файл

@ -121,12 +121,9 @@ add_task(async function test_ensureLoggedIn() {
let log = Log.repository.getLogger("Test");
Log.repository.rootLogger.addAppender(new Log.DumpAppender());
let identityConfig = makeIdentityConfig();
let browseridManager = new BrowserIDManager();
configureFxAccountIdentity(browseridManager, identityConfig);
await browseridManager.ensureLoggedIn();
await configureIdentity();
let keyBundle = browseridManager.syncKeyBundle;
let keyBundle = Weave.Service.identity.syncKeyBundle;
/*
* Build a test version of storage/crypto/keys.

Просмотреть файл

@ -16,13 +16,12 @@ add_task(async function test_findCluster() {
Service.identity._ensureValidToken = () => Promise.reject(new Error("Connection refused"));
_("_findCluster() throws on network errors (e.g. connection refused).");
await Assert.rejects(Service._clusterManager._findCluster());
await Assert.rejects(Service.identity._findCluster());
Service.identity._ensureValidToken = () => Promise.resolve(true);
Service.identity._token = { endpoint: "http://weave.user.node" };
Service.identity._ensureValidToken = () => Promise.resolve({ endpoint: "http://weave.user.node" });
_("_findCluster() returns the user's cluster node");
let cluster = await Service._clusterManager._findCluster();
let cluster = await Service.identity._findCluster();
Assert.equal(cluster, "http://weave.user.node/");
} finally {
@ -37,19 +36,19 @@ add_task(async function test_setCluster() {
_("Check initial state.");
Assert.equal(Service.clusterURL, "");
Service._clusterManager._findCluster = () => "http://weave.user.node/";
Service.identity._findCluster = () => "http://weave.user.node/";
_("Set the cluster URL.");
Assert.ok((await Service._clusterManager.setCluster()));
Assert.ok((await Service.identity.setCluster()));
Assert.equal(Service.clusterURL, "http://weave.user.node/");
_("Setting it again won't make a difference if it's the same one.");
Assert.ok(!(await Service._clusterManager.setCluster()));
Assert.ok(!(await Service.identity.setCluster()));
Assert.equal(Service.clusterURL, "http://weave.user.node/");
_("A 'null' response won't make a difference either.");
Service._clusterManager._findCluster = () => null;
Assert.ok(!(await Service._clusterManager.setCluster()));
Service.identity._findCluster = () => null;
Assert.ok(!(await Service.identity.setCluster()));
Assert.equal(Service.clusterURL, "http://weave.user.node/");
} finally {
Svc.Prefs.resetBranch("");

Просмотреть файл

@ -84,7 +84,7 @@ add_task(async function test_verifyLogin() {
_("Ensure a network error when finding the cluster sets the right Status bits.");
Service.status.resetSync();
Service.clusterURL = "";
Service._clusterManager._findCluster = () => "http://localhost:12345/";
Service.identity._findCluster = () => "http://localhost:12345/";
Assert.equal(false, (await Service.verifyLogin()));
Assert.equal(Service.status.service, LOGIN_FAILED);
Assert.equal(Service.status.login, LOGIN_FAILED_NETWORK_ERROR);

Просмотреть файл

@ -531,6 +531,9 @@ add_task(async function test_autoconnect_mp_locked() {
Service.identity._fxaService = new FxAccounts({
canGetKeys() {
return false;
},
getSignedInUser() {
return origFxA.getSignedInUser();
}
});
@ -716,8 +719,8 @@ add_task(async function test_no_sync_node() {
let server = sync_httpd_setup();
await setUp(server);
let oldfc = Service._clusterManager._findCluster;
Service._clusterManager._findCluster = () => null;
let oldfc = Service.identity._findCluster;
Service.identity._findCluster = () => null;
Service.clusterURL = "";
try {
await Service.sync();
@ -726,7 +729,7 @@ add_task(async function test_no_sync_node() {
await cleanUpAndGo(server);
} finally {
Service._clusterManager._findCluster = oldfc;
Service.identity._findCluster = oldfc;
}
});

Просмотреть файл

@ -29,7 +29,7 @@
"collection_repair.js": ["getRepairRequestor", "getAllRepairRequestors", "CollectionRepairRequestor", "getRepairResponder", "CollectionRepairResponder"],
"collection_validator.js": ["CollectionValidator", "CollectionProblemData"],
"Console.jsm": ["console", "ConsoleAPI"],
"constants.js": ["WEAVE_VERSION", "SYNC_API_VERSION", "STORAGE_VERSION", "PREFS_BRANCH", "PWDMGR_HOST", "PWDMGR_PASSWORD_REALM", "PWDMGR_PASSPHRASE_REALM", "PWDMGR_KEYBUNDLE_REALM", "DEFAULT_KEYBUNDLE_NAME", "SYNC_KEY_ENCODED_LENGTH", "SYNC_KEY_DECODED_LENGTH", "NO_SYNC_NODE_INTERVAL", "MAX_ERROR_COUNT_BEFORE_BACKOFF", "MINIMUM_BACKOFF_INTERVAL", "MAXIMUM_BACKOFF_INTERVAL", "HMAC_EVENT_INTERVAL", "MASTER_PASSWORD_LOCKED_RETRY_INTERVAL", "DEFAULT_GUID_FETCH_BATCH_SIZE", "DEFAULT_DOWNLOAD_BATCH_SIZE", "SINGLE_USER_THRESHOLD", "MULTI_DEVICE_THRESHOLD", "SCORE_INCREMENT_SMALL", "SCORE_INCREMENT_MEDIUM", "SCORE_INCREMENT_XLARGE", "SCORE_UPDATE_DELAY", "IDLE_OBSERVER_BACK_DELAY", "URI_LENGTH_MAX", "MAX_HISTORY_UPLOAD", "MAX_HISTORY_DOWNLOAD", "STATUS_OK", "SYNC_FAILED", "LOGIN_FAILED", "SYNC_FAILED_PARTIAL", "CLIENT_NOT_CONFIGURED", "STATUS_DISABLED", "MASTER_PASSWORD_LOCKED", "LOGIN_SUCCEEDED", "SYNC_SUCCEEDED", "ENGINE_SUCCEEDED", "LOGIN_FAILED_NO_USERNAME", "LOGIN_FAILED_NO_PASSPHRASE", "LOGIN_FAILED_NETWORK_ERROR", "LOGIN_FAILED_SERVER_ERROR", "LOGIN_FAILED_INVALID_PASSPHRASE", "LOGIN_FAILED_LOGIN_REJECTED", "METARECORD_DOWNLOAD_FAIL", "VERSION_OUT_OF_DATE", "CREDENTIALS_CHANGED", "ABORT_SYNC_COMMAND", "NO_SYNC_NODE_FOUND", "OVER_QUOTA", "PROLONGED_SYNC_FAILURE", "SERVER_MAINTENANCE", "RESPONSE_OVER_QUOTA", "ENGINE_UPLOAD_FAIL", "ENGINE_DOWNLOAD_FAIL", "ENGINE_UNKNOWN_FAIL", "ENGINE_APPLY_FAIL", "ENGINE_BATCH_INTERRUPTED", "kSyncMasterPasswordLocked", "kSyncWeaveDisabled", "kSyncNetworkOffline", "kSyncBackoffNotMet", "kFirstSyncChoiceNotMade", "kSyncNotConfigured", "kFirefoxShuttingDown", "DEVICE_TYPE_DESKTOP", "DEVICE_TYPE_MOBILE", "SQLITE_MAX_VARIABLE_NUMBER"],
"constants.js": ["WEAVE_VERSION", "SYNC_API_VERSION", "STORAGE_VERSION", "PREFS_BRANCH", "DEFAULT_KEYBUNDLE_NAME", "SYNC_KEY_ENCODED_LENGTH", "SYNC_KEY_DECODED_LENGTH", "NO_SYNC_NODE_INTERVAL", "MAX_ERROR_COUNT_BEFORE_BACKOFF", "MINIMUM_BACKOFF_INTERVAL", "MAXIMUM_BACKOFF_INTERVAL", "HMAC_EVENT_INTERVAL", "MASTER_PASSWORD_LOCKED_RETRY_INTERVAL", "DEFAULT_GUID_FETCH_BATCH_SIZE", "DEFAULT_DOWNLOAD_BATCH_SIZE", "SINGLE_USER_THRESHOLD", "MULTI_DEVICE_THRESHOLD", "SCORE_INCREMENT_SMALL", "SCORE_INCREMENT_MEDIUM", "SCORE_INCREMENT_XLARGE", "SCORE_UPDATE_DELAY", "IDLE_OBSERVER_BACK_DELAY", "URI_LENGTH_MAX", "MAX_HISTORY_UPLOAD", "MAX_HISTORY_DOWNLOAD", "STATUS_OK", "SYNC_FAILED", "LOGIN_FAILED", "SYNC_FAILED_PARTIAL", "CLIENT_NOT_CONFIGURED", "STATUS_DISABLED", "MASTER_PASSWORD_LOCKED", "LOGIN_SUCCEEDED", "SYNC_SUCCEEDED", "ENGINE_SUCCEEDED", "LOGIN_FAILED_NO_USERNAME", "LOGIN_FAILED_NO_PASSPHRASE", "LOGIN_FAILED_NETWORK_ERROR", "LOGIN_FAILED_SERVER_ERROR", "LOGIN_FAILED_INVALID_PASSPHRASE", "LOGIN_FAILED_LOGIN_REJECTED", "METARECORD_DOWNLOAD_FAIL", "VERSION_OUT_OF_DATE", "CREDENTIALS_CHANGED", "ABORT_SYNC_COMMAND", "NO_SYNC_NODE_FOUND", "OVER_QUOTA", "PROLONGED_SYNC_FAILURE", "SERVER_MAINTENANCE", "RESPONSE_OVER_QUOTA", "ENGINE_UPLOAD_FAIL", "ENGINE_DOWNLOAD_FAIL", "ENGINE_UNKNOWN_FAIL", "ENGINE_APPLY_FAIL", "ENGINE_BATCH_INTERRUPTED", "kSyncMasterPasswordLocked", "kSyncWeaveDisabled", "kSyncNetworkOffline", "kSyncBackoffNotMet", "kFirstSyncChoiceNotMade", "kSyncNotConfigured", "kFirefoxShuttingDown", "DEVICE_TYPE_DESKTOP", "DEVICE_TYPE_MOBILE", "SQLITE_MAX_VARIABLE_NUMBER"],
"Constants.jsm": ["Roles", "Events", "Relations", "Filters", "States", "Prefilters"],
"ContactDB.jsm": ["ContactDB", "DB_NAME", "STORE_NAME", "SAVED_GETALL_STORE_NAME", "REVISION_STORE", "DB_VERSION"],
"content-server.jsm": ["init"],