diff options
Diffstat (limited to 'services/sync/modules/browserid_identity.js')
-rw-r--r-- | services/sync/modules/browserid_identity.js | 151 |
1 files changed, 34 insertions, 117 deletions
diff --git a/services/sync/modules/browserid_identity.js b/services/sync/modules/browserid_identity.js index db3821518..bc8ea6b30 100644 --- a/services/sync/modules/browserid_identity.js +++ b/services/sync/modules/browserid_identity.js @@ -4,9 +4,9 @@ "use strict"; -this.EXPORTED_SYMBOLS = ["BrowserIDManager", "AuthenticationError"]; +this.EXPORTED_SYMBOLS = ["BrowserIDManager"]; -var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; Cu.import("resource://gre/modules/Log.jsm"); Cu.import("resource://services-common/async.js"); @@ -39,13 +39,12 @@ XPCOMUtils.defineLazyGetter(this, 'log', function() { }); // FxAccountsCommon.js doesn't use a "namespace", so create one here. -var fxAccountsCommon = {}; +let fxAccountsCommon = {}; Cu.import("resource://gre/modules/FxAccountsCommon.js", fxAccountsCommon); const OBSERVER_TOPICS = [ fxAccountsCommon.ONLOGIN_NOTIFICATION, fxAccountsCommon.ONLOGOUT_NOTIFICATION, - fxAccountsCommon.ON_ACCOUNT_STATE_CHANGE_NOTIFICATION, ]; const PREF_SYNC_SHOW_CUSTOMIZATION = "services.sync-setup.ui.showCustomizationDialog"; @@ -66,9 +65,8 @@ function deriveKeyBundle(kB) { some other error object (which should do the right thing when toString() is called on it) */ -function AuthenticationError(details, source) { +function AuthenticationError(details) { this.details = details; - this.source = source; } AuthenticationError.prototype = { @@ -106,6 +104,12 @@ this.BrowserIDManager.prototype = { // we don't consider the lack of a keybundle as a failure state. _shouldHaveSyncKeyBundle: false, + get readyToAuthenticate() { + // We are finished initializing when we *should* have a sync key bundle, + // although we might not actually have one due to auth failures etc. + return this._shouldHaveSyncKeyBundle; + }, + get needsCustomization() { try { return Services.prefs.getBoolPref(PREF_SYNC_SHOW_CUSTOMIZATION); @@ -114,34 +118,11 @@ this.BrowserIDManager.prototype = { } }, - hashedUID() { - if (!this._token) { - throw new Error("hashedUID: Don't have token"); - } - return this._token.hashed_fxa_uid - }, - - deviceID() { - return this._signedInUser && this._signedInUser.deviceId; - }, - initialize: function() { for (let topic of OBSERVER_TOPICS) { Services.obs.addObserver(this, topic, false); } - // and a background fetch of account data just so we can set this.account, - // so we have a username available before we've actually done a login. - // XXX - this is actually a hack just for tests and really shouldn't be - // necessary. Also, you'd think it would be safe to allow this.account to - // be set to null when there's no user logged in, but argue with the test - // suite, not with me :) - this._fxaService.getSignedInUser().then(accountData => { - if (accountData) { - this.account = accountData.email; - } - }).catch(err => { - // As above, this is only for tests so it is safe to ignore. - }); + return this.initializeWithCurrentIdentity(); }, /** @@ -149,7 +130,7 @@ this.BrowserIDManager.prototype = { * the user is logged in, or is rejected if the login attempt has failed. */ ensureLoggedIn: function() { - if (!this._shouldHaveSyncKeyBundle && this.whenReadyToAuthenticate) { + if (!this._shouldHaveSyncKeyBundle) { // We are already in the process of logging in. return this.whenReadyToAuthenticate.promise; } @@ -163,7 +144,7 @@ this.BrowserIDManager.prototype = { // 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")); + return Promise.reject(); } // So - we've a previous auth problem and aren't currently attempting to @@ -179,6 +160,7 @@ this.BrowserIDManager.prototype = { } this.resetCredentials(); this._signedInUser = null; + return Promise.resolve(); }, offerSyncOptions: function () { @@ -202,7 +184,7 @@ this.BrowserIDManager.prototype = { // Reset the world before we do anything async. this.whenReadyToAuthenticate = Promise.defer(); - this.whenReadyToAuthenticate.promise.catch(err => { + this.whenReadyToAuthenticate.promise.then(null, (err) => { this._log.error("Could not authenticate", err); }); @@ -258,14 +240,14 @@ this.BrowserIDManager.prototype = { Services.obs.notifyObservers(null, "weave:service:setup-complete", null); Weave.Utils.nextTick(Weave.Service.sync, Weave.Service); } - }).catch(authErr => { - // report what failed... - this._log.error("Background fetch for key bundle failed", authErr); + }).then(null, err => { this._shouldHaveSyncKeyBundle = true; // but we probably don't have one... - this.whenReadyToAuthenticate.reject(authErr); + this.whenReadyToAuthenticate.reject(err); + // report what failed... + this._log.error("Background fetch for key bundle failed", err); }); // and we are done - the fetch continues on in the background... - }).catch(err => { + }).then(null, err => { this._log.error("Processing logged in account", err); }); }, @@ -301,8 +283,7 @@ this.BrowserIDManager.prototype = { // reauth with the server - in that case we will also get here, but // should have the same identity. // initializeWithCurrentIdentity will throw and log if these constraints - // aren't met (indirectly, via _updateSignedInUser()), so just go ahead - // and do the init. + // aren't met, so just go ahead and do the init. this.initializeWithCurrentIdentity(true); break; @@ -311,13 +292,6 @@ this.BrowserIDManager.prototype = { // 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 - this.resetCredentials(); - this._ensureValidToken().catch(err => - this._log.error("Error while fetching a new token", err)); - break; } }, @@ -413,9 +387,6 @@ this.BrowserIDManager.prototype = { resetCredentials: function() { this.resetSyncKey(); this._token = null; - // The cluster URL comes from the token, so resetting it to empty will - // force Sync to not accidentally use a value from an earlier token. - Weave.Service.clusterURL = null; }, /** @@ -503,12 +474,7 @@ this.BrowserIDManager.prototype = { // 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 (this._canFetchKeys()) { - result = STATUS_OK; - } else { - result = LOGIN_FAILED_LOGIN_REJECTED; - } + let result = this._canFetchKeys() ? STATUS_OK : LOGIN_FAILED_LOGIN_REJECTED; log.debug("unlockAndVerifyAuthState re-fetched credentials and is returning", result); return result; } @@ -540,27 +506,14 @@ this.BrowserIDManager.prototype = { return true; }, - // Get our tokenServerURL - a private helper. Returns a string. - get _tokenServerUrl() { - // We used to support services.sync.tokenServerURI but this was a - // pain-point for people using non-default servers as Sync may auto-reset - // all services.sync prefs. So if that still exists, it wins. - let url = Svc.Prefs.get("tokenServerURI"); // Svc.Prefs "root" is services.sync - if (!url) { - url = Services.prefs.getCharPref("identity.sync.tokenserver.uri"); - } - while (url.endsWith("/")) { // trailing slashes cause problems... - url = url.slice(0, -1); - } - return url; - }, - // 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. _fetchTokenForUser: function() { - // tokenServerURI is mis-named - convention is uri means nsISomething... - let tokenServerURI = this._tokenServerUrl; + let tokenServerURI = Svc.Prefs.get("tokenServerURI"); + if (tokenServerURI.endsWith("/")) { // trailing slashes cause problems... + tokenServerURI = tokenServerURI.slice(0, -1); + } let log = this._log; let client = this._tokenServerClient; let fxa = this._fxaService; @@ -589,7 +542,7 @@ this.BrowserIDManager.prototype = { ); } - let getToken = assertion => { + let getToken = (tokenServerURI, assertion) => { log.debug("Getting a token"); let deferred = Promise.defer(); let cb = function (err, token) { @@ -617,18 +570,7 @@ this.BrowserIDManager.prototype = { return fxa.whenVerified(this._signedInUser) .then(() => maybeFetchKeys()) .then(() => getAssertion()) - .then(assertion => getToken(assertion)) - .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(assertion => getToken(tokenServerURI, 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 @@ -640,18 +582,15 @@ this.BrowserIDManager.prototype = { } return token; }) - .catch(err => { + .then(null, 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"); + err = new AuthenticationError(err); // 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"); + err = new AuthenticationError(err); } // TODO: write tests to make sure that different auth error cases are handled here @@ -673,6 +612,7 @@ this.BrowserIDManager.prototype = { // that there is no authentication dance still under way. this._shouldHaveSyncKeyBundle = true; Weave.Status.login = this._authFailureReason; + Services.obs.notifyObservers(null, "weave:ui:login:error", null); throw err; }); }, @@ -684,19 +624,12 @@ this.BrowserIDManager.prototype = { this._log.debug("_ensureValidToken already has one"); return Promise.resolve(); } - const notifyStateChanged = - () => Services.obs.notifyObservers(null, "weave:service:login:change", null); // 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; - notifyStateChanged(); - }, - error => { - notifyStateChanged(); - throw error } ); }, @@ -719,16 +652,9 @@ this.BrowserIDManager.prototype = { _getAuthenticationHeader: function(httpObject, method) { let cb = Async.makeSpinningCallback(); this._ensureValidToken().then(cb, cb); - // Note that in failure states we return null, causing the request to be - // made without authorization headers, thereby presumably causing a 401, - // which causes Sync to log out. If we throw, this may not happen as - // expected. try { cb.wait(); } catch (ex) { - if (Async.isShutdownException(ex)) { - throw ex; - } this._log.error("Failed to fetch a token for authentication", ex); return null; } @@ -764,17 +690,8 @@ this.BrowserIDManager.prototype = { createClusterManager: function(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 @@ -820,7 +737,7 @@ BrowserIDClusterManager.prototype = { // 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"); + log.debug("_findCluster found existing clusterURL, so discarding the current token"); this.identity._token = null; } return this.identity._ensureValidToken(); |