--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1619,16 +1619,18 @@ pref("loop.debug.sdk", false);
#ifdef DEBUG
pref("loop.CSP", "default-src 'self' about: file: chrome: http://localhost:*; img-src 'self' data: http://www.gravatar.com/ about: file: chrome:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net http://localhost:* ws://localhost:*");
#else
pref("loop.CSP", "default-src 'self' about: file: chrome:; img-src 'self' data: http://www.gravatar.com/ about: file: chrome:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net");
#endif
pref("loop.oauth.google.redirect_uri", "urn:ietf:wg:oauth:2.0:oob:auto");
pref("loop.oauth.google.scope", "https://www.google.com/m8/feeds");
pref("loop.rooms.enabled", false);
+pref("loop.fxa_oauth.tokendata", "");
+pref("loop.fxa_oauth.profile", "");
// serverURL to be assigned by services team
pref("services.push.serverURL", "wss://push.services.mozilla.com/");
pref("social.sidebar.unload_timeout_ms", 10000);
pref("dom.identity.enabled", false);
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -72,29 +72,37 @@ XPCOMUtils.defineLazyGetter(this, "log",
let ConsoleAPI = Cu.import("resource://gre/modules/devtools/Console.jsm", {}).ConsoleAPI;
let consoleOptions = {
maxLogLevel: Services.prefs.getCharPref(PREF_LOG_LEVEL).toLowerCase(),
prefix: "Loop",
};
return new ConsoleAPI(consoleOptions);
});
+function setJSONPref(aName, aValue) {
+ let value = !!aValue ? JSON.stringify(aValue) : "";
+ Services.prefs.setCharPref(aName, value);
+}
+
+function getJSONPref(aName) {
+ let value = Services.prefs.getCharPref(aName);
+ return !!value ? JSON.parse(value) : null;
+}
+
// The current deferred for the registration process. This is set if in progress
// or the registration was successful. This is null if a registration attempt was
// unsuccessful.
let gRegisteredDeferred = null;
let gPushHandler = null;
let gHawkClient = null;
-let gLocalizedStrings = null;
+let gLocalizedStrings = null;
let gInitializeTimer = null;
let gFxAEnabled = true;
let gFxAOAuthClientPromise = null;
let gFxAOAuthClient = null;
-let gFxAOAuthTokenData = null;
-let gFxAOAuthProfile = null;
let gErrors = new Map();
/**
* Attempts to open a websocket.
*
* A new websocket interface is used each time. If an onStop callback
* was received, calling asyncOpen() on the same interface will
* trigger a "alreay open socket" exception even though the channel
@@ -302,16 +310,48 @@ let MozLoopServiceInternal = {
/**
* Returns true if the expiry time is in the future.
*/
urlExpiryTimeIsInFuture: function() {
return this.expiryTimeSeconds * 1000 > Date.now();
},
/**
+ * Retrieves MozLoopService Firefox Accounts OAuth token.
+ *
+ * @return {Object} OAuth token
+ */
+ get fxAOAuthTokenData() {
+ return getJSONPref("loop.fxa_oauth.tokendata");
+ },
+
+ /**
+ * Sets MozLoopService Firefox Accounts OAuth token.
+ * If the tokenData is being cleared, will also clear the
+ * profile since the profile is dependent on the token data.
+ *
+ * @param {Object} aTokenData OAuth token
+ */
+ set fxAOAuthTokenData(aTokenData) {
+ setJSONPref("loop.fxa_oauth.tokendata", aTokenData);
+ if (!aTokenData) {
+ this.fxAOAuthProfile = null;
+ }
+ },
+
+ /**
+ * Sets MozLoopService Firefox Accounts Profile data.
+ *
+ * @param {Object} aProfileData Profile data
+ */
+ set fxAOAuthProfile(aProfileData) {
+ setJSONPref("loop.fxa_oauth.profile", aProfileData);
+ },
+
+ /**
* Retrieves MozLoopService "do not disturb" pref value.
*
* @return {Boolean} aFlag
*/
get doNotDisturb() {
return Services.prefs.getBoolPref("loop.do_not_disturb");
},
@@ -415,19 +455,18 @@ let MozLoopServiceInternal = {
}
gRegisteredDeferred = Promise.defer();
// We grab the promise early in case .initialize or its results sets
// it back to null on error.
let result = gRegisteredDeferred.promise;
gPushHandler = mockPushHandler || MozLoopPushHandler;
-
gPushHandler.initialize(this.onPushRegistered.bind(this),
- this.onHandleNotification.bind(this));
+ this.onHandleNotification.bind(this));
return result;
},
/**
* Performs a hawk based request to the loop server.
*
* @param {LOOP_SESSION_TYPE} sessionType The type of session to use for the request.
@@ -570,22 +609,25 @@ let MozLoopServiceInternal = {
return;
}
this.registerWithLoopServer(LOOP_SESSION_TYPE.GUEST, pushUrl).then(() => {
// storeSessionToken could have rejected and nulled the promise if the token was malformed.
if (!gRegisteredDeferred) {
return;
}
- gRegisteredDeferred.resolve();
+ gRegisteredDeferred.resolve("registered to guest status");
// No need to clear the promise here, everything was good, so we don't need
// to re-register.
- }, (error) => {
+ }, error => {
log.error("Failed to register with Loop server: ", error);
- gRegisteredDeferred.reject(error.errno);
+ // registerWithLoopServer may have already made this null.
+ if (gRegisteredDeferred) {
+ gRegisteredDeferred.reject(error);
+ }
gRegisteredDeferred = null;
});
},
/**
* Registers with the Loop server either as a guest or a FxA user.
*
* @param {LOOP_SESSION_TYPE} sessionType The type of session e.g. guest or FxA
@@ -611,16 +653,18 @@ let MozLoopServiceInternal = {
// Authorization failed, invalid token, we need to try again with a new token.
if (retry) {
return this.registerWithLoopServer(sessionType, pushUrl, false);
}
}
log.error("Failed to register with the loop server. Error: ", error);
this.setError("registration", error);
+ gRegisteredDeferred.reject(error);
+ gRegisteredDeferred = null;
throw error;
}
);
},
/**
* Unregisters from the Loop server either as a guest or a FxA user.
*
@@ -1064,66 +1108,101 @@ let MozLoopServiceInternal = {
deferred.resolve(result);
} else {
deferred.reject("Invalid token data");
}
},
};
Object.freeze(MozLoopServiceInternal);
-let gInitializeTimerFunc = () => {
- // Kick off the push notification service into registering after a timeout
- // this ensures we're not doing too much straight after the browser's finished
+let gInitializeTimerFunc = (deferredInitialization, mockPushHandler, mockWebSocket) => {
+ // Kick off the push notification service into registering after a timeout.
+ // This ensures we're not doing too much straight after the browser's finished
// starting up.
gInitializeTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
- gInitializeTimer.initWithCallback(() => {
- MozLoopService.register();
+ gInitializeTimer.initWithCallback(Task.async(function* initializationCallback() {
+ yield MozLoopService.register(mockPushHandler, mockWebSocket).then(Task.async(function*() {
+ if (!MozLoopServiceInternal.fxAOAuthTokenData) {
+ log.debug("MozLoopService: Initialized without an already logged-in account");
+ deferredInitialization.resolve("initialized to guest status");
+ return;
+ }
+
+ log.debug("MozLoopService: Initializing with already logged-in account");
+ let registeredPromise =
+ MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA,
+ gPushHandler.pushUrl);
+ registeredPromise.then(() => {
+ deferredInitialization.resolve("initialized to logged-in status");
+ }, error => {
+ log.debug("MozLoopService: error logging in using cached auth token");
+ MozLoopServiceInternal.setError("login", error);
+ deferredInitialization.reject("error logging in using cached auth token");
+ });
+ }), error => {
+ log.debug("MozLoopService: Failure of initial registration", error);
+ deferredInitialization.reject(error);
+ });
gInitializeTimer = null;
- },
+ }),
MozLoopServiceInternal.initialRegistrationDelayMilliseconds, Ci.nsITimer.TYPE_ONE_SHOT);
};
/**
* Public API
*/
this.MozLoopService = {
_DNSService: gDNSService,
set initializeTimerFunc(value) {
gInitializeTimerFunc = value;
},
/**
* Initialized the loop service, and starts registration with the
* push and loop servers.
+ *
+ * @return {Promise}
*/
- initialize: function() {
-
+ initialize: Task.async(function*(mockPushHandler, mockWebSocket) {
// Do this here, rather than immediately after definition, so that we can
// stub out API functions for unit testing
Object.freeze(this);
// Don't do anything if loop is not enabled.
if (!Services.prefs.getBoolPref("loop.enabled") ||
Services.prefs.getBoolPref("loop.throttled")) {
- return;
+ return Promise.reject("loop is not enabled");
}
if (Services.prefs.getPrefType("loop.fxa.enabled") == Services.prefs.PREF_BOOL) {
gFxAEnabled = Services.prefs.getBoolPref("loop.fxa.enabled");
if (!gFxAEnabled) {
- this.logOutFromFxA();
+ yield this.logOutFromFxA();
}
}
- // If expiresTime is in the future then kick-off registration.
- if (MozLoopServiceInternal.urlExpiryTimeIsInFuture()) {
- gInitializeTimerFunc();
+ // If expiresTime is not in the future and the user hasn't
+ // previously authenticated then skip registration.
+ if (!MozLoopServiceInternal.urlExpiryTimeIsInFuture() &&
+ !MozLoopServiceInternal.fxAOAuthTokenData) {
+ return Promise.resolve("registration not needed");
}
- },
+
+ let deferredInitialization = Promise.defer();
+ gInitializeTimerFunc(deferredInitialization, mockPushHandler, mockWebSocket);
+
+ return deferredInitialization.promise.catch(error => {
+ if (typeof(error) == "object") {
+ // This never gets cleared since there is no UI to recover. Only restarting will work.
+ MozLoopServiceInternal.setError("initialization", error);
+ }
+ throw error;
+ });
+ }),
/**
* If we're operating the service in "soft start" mode, and this browser
* isn't already activated, check whether it's time for it to become active.
* If so, activate the loop service.
*
* @param {Object} buttonNode DOM node representing the Loop button -- if we
* change from inactive to active, we need this
@@ -1243,17 +1322,17 @@ this.MozLoopService = {
},
/**
* Used to note a call url expiry time. If the time is later than the current
* latest expiry time, then the stored expiry time is increased. For times
* sooner, this function is a no-op; this ensures we always have the latest
* expiry time for a url.
*
- * This is used to deterimine whether or not we should be registering with the
+ * This is used to determine whether or not we should be registering with the
* push server on start.
*
* @param {Integer} expiryTimeSeconds The seconds since epoch of the expiry time
* of the url.
*/
noteCallUrlExpiry: function(expiryTimeSeconds) {
MozLoopServiceInternal.expiryTimeSeconds = expiryTimeSeconds;
},
@@ -1300,18 +1379,26 @@ this.MozLoopService = {
set doNotDisturb(aFlag) {
MozLoopServiceInternal.doNotDisturb = aFlag;
},
get fxAEnabled() {
return gFxAEnabled;
},
+ /**
+ * Gets the user profile, but only if there is
+ * tokenData present. Without tokenData, the
+ * profile is meaningless.
+ *
+ * @return {Object}
+ */
get userProfile() {
- return gFxAOAuthProfile;
+ return getJSONPref("loop.fxa_oauth.tokendata") &&
+ getJSONPref("loop.fxa_oauth.profile");
},
get errors() {
return MozLoopServiceInternal.errors;
},
get log() {
return log;
@@ -1430,55 +1517,55 @@ this.MozLoopService = {
/**
* Start the FxA login flow using the OAuth client and params from the Loop server.
*
* The caller should be prepared to handle rejections related to network, server or login errors.
*
* @return {Promise} that resolves when the FxA login flow is complete.
*/
logInToFxA: function() {
- log.debug("logInToFxA with gFxAOAuthTokenData:", !!gFxAOAuthTokenData);
- if (gFxAOAuthTokenData) {
- return Promise.resolve(gFxAOAuthTokenData);
+ log.debug("logInToFxA with fxAOAuthTokenData:", !!MozLoopServiceInternal.fxAOAuthTokenData);
+ if (MozLoopServiceInternal.fxAOAuthTokenData) {
+ return Promise.resolve(MozLoopServiceInternal.fxAOAuthTokenData);
}
return MozLoopServiceInternal.promiseFxAOAuthAuthorization().then(response => {
return MozLoopServiceInternal.promiseFxAOAuthToken(response.code, response.state);
}).then(tokenData => {
- gFxAOAuthTokenData = tokenData;
+ MozLoopServiceInternal.fxAOAuthTokenData = tokenData;
return tokenData;
}).then(tokenData => {
return gRegisteredDeferred.promise.then(Task.async(function*() {
if (gPushHandler.pushUrl) {
yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, gPushHandler.pushUrl);
} else {
throw new Error("No pushUrl for FxA registration");
}
MozLoopServiceInternal.clearError("login");
MozLoopServiceInternal.clearError("profile");
- return gFxAOAuthTokenData;
+ return MozLoopServiceInternal.fxAOAuthTokenData;
}));
}).then(tokenData => {
let client = new FxAccountsProfileClient({
serverURL: gFxAOAuthClient.parameters.profile_uri,
token: tokenData.access_token
});
client.fetchProfile().then(result => {
- gFxAOAuthProfile = result;
+ MozLoopServiceInternal.fxAOAuthProfile = result;
MozLoopServiceInternal.notifyStatusChanged("login");
}, error => {
log.error("Failed to retrieve profile", error);
this.setError("profile", error);
- gFxAOAuthProfile = null;
+ MozLoopServiceInternal.fxAOAuthProfile = null;
MozLoopServiceInternal.notifyStatusChanged();
});
return tokenData;
}).catch(error => {
- gFxAOAuthTokenData = null;
- gFxAOAuthProfile = null;
+ MozLoopServiceInternal.fxAOAuthTokenData = null;
+ MozLoopServiceInternal.fxAOAuthProfile = null;
throw error;
}).catch((error) => {
MozLoopServiceInternal.setError("login", error);
// Re-throw for testing
throw error;
});
},
@@ -1493,18 +1580,18 @@ this.MozLoopService = {
log.debug("logOutFromFxA");
if (gPushHandler && gPushHandler.pushUrl) {
yield MozLoopServiceInternal.unregisterFromLoopServer(LOOP_SESSION_TYPE.FXA,
gPushHandler.pushUrl);
} else {
MozLoopServiceInternal.clearSessionToken(LOOP_SESSION_TYPE.FXA);
}
- gFxAOAuthTokenData = null;
- gFxAOAuthProfile = null;
+ MozLoopServiceInternal.fxAOAuthTokenData = null;
+ MozLoopServiceInternal.fxAOAuthProfile = null;
// Reset the client since the initial promiseFxAOAuthParameters() call is
// what creates a new session.
gFxAOAuthClient = null;
gFxAOAuthClientPromise = null;
// clearError calls notifyStatusChanged so should be done last when the
// state is clean.
--- a/browser/components/loop/test/mochitest/browser_fxa_login.js
+++ b/browser/components/loop/test/mochitest/browser_fxa_login.js
@@ -2,21 +2,16 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test FxA logins with Loop.
*/
"use strict";
-const {
- gFxAOAuthTokenData,
- gFxAOAuthProfile,
-} = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
-
const BASE_URL = "http://mochi.test:8888/browser/browser/components/loop/test/mochitest/loop_fxa.sjs?";
function* checkFxA401() {
let err = MozLoopService.errors.get("login");
ise(err.code, 401, "Check error code");
ise(err.friendlyMessage, getLoopString("could_not_authenticate"),
"Check friendlyMessage");
ise(err.friendlyDetails, getLoopString("password_changed_question"),
@@ -206,16 +201,18 @@ add_task(function* registrationWithInval
Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH));
let tokenPromise = MozLoopServiceInternal.promiseFxAOAuthToken("code1", "state");
yield tokenPromise.then(body => {
ok(false, "Promise should have rejected");
},
error => {
is(error.code, 400, "Check error code");
+ checkFxAOAuthTokenData(null);
+ is(MozLoopService.userProfile, null, "Profile should be empty after invalid login");
});
});
add_task(function* registrationWith401() {
yield resetFxA();
let params = {
client_id: "client_id",
content_uri: BASE_URL + "/content",
@@ -227,16 +224,18 @@ add_task(function* registrationWith401()
yield promiseOAuthParamsSetup(BASE_URL, params);
let tokenPromise = MozLoopServiceInternal.promiseFxAOAuthToken("code1", "state");
yield tokenPromise.then(body => {
ok(false, "Promise should have rejected");
},
error => {
is(error.code, 401, "Check error code");
+ checkFxAOAuthTokenData(null);
+ is(MozLoopService.userProfile, null, "Profile should be empty after invalid login");
});
yield checkFxA401();
});
add_task(function* basicAuthorizationAndRegistration() {
yield resetFxA();
let params = {
@@ -316,17 +315,17 @@ add_task(function* loginWithParams401()
yield MozLoopService.register(mockPushHandler);
let loginPromise = MozLoopService.logInToFxA();
yield loginPromise.then(tokenData => {
ok(false, "Promise should have rejected");
},
error => {
ise(error.code, 401, "Check error code");
- ise(gFxAOAuthTokenData, null, "Check there is no saved token data");
+ checkFxAOAuthTokenData(null);
});
yield checkFxA401();
});
add_task(function* logoutWithIncorrectPushURL() {
yield resetFxA();
let pushURL = "http://www.example.com/";
@@ -382,13 +381,13 @@ add_task(function* loginWithRegistration
yield promiseOAuthParamsSetup(BASE_URL, params);
let loginPromise = MozLoopService.logInToFxA();
yield loginPromise.then(tokenData => {
ok(false, "Promise should have rejected");
},
error => {
ise(error.code, 401, "Check error code");
- ise(gFxAOAuthTokenData, null, "Check there is no saved token data");
+ checkFxAOAuthTokenData(null);
});
yield checkFxA401();
});
--- a/browser/components/loop/test/mochitest/browser_toolbarbutton.js
+++ b/browser/components/loop/test/mochitest/browser_toolbarbutton.js
@@ -4,74 +4,76 @@
/**
* Test the toolbar button states.
*/
"use strict";
registerCleanupFunction(function*() {
MozLoopService.doNotDisturb = false;
- setInternalLoopGlobal("gFxAOAuthProfile", null);
+ MozLoopServiceInternal.fxAOAuthProfile = null;
yield MozLoopServiceInternal.clearError("testing");
});
add_task(function* test_doNotDisturb() {
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
yield MozLoopService.doNotDisturb = true;
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "disabled", "Check button is in disabled state");
yield MozLoopService.doNotDisturb = false;
Assert.notStrictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "disabled", "Check button is not in disabled state");
});
add_task(function* test_doNotDisturb_with_login() {
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
yield MozLoopService.doNotDisturb = true;
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "disabled", "Check button is in disabled state");
- setInternalLoopGlobal("gFxAOAuthProfile", {email: "test@example.com", uid: "abcd1234"});
+ MozLoopServiceInternal.fxAOAuthTokenData = {token_type:"bearer",access_token:"1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1752",scope:"profile"};
+ MozLoopServiceInternal.fxAOAuthProfile = {email: "test@example.com", uid: "abcd1234"};
yield MozLoopServiceInternal.notifyStatusChanged("login");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "active", "Check button is in active state");
yield loadLoopPanel();
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "disabled", "Check button is in disabled state after opening panel");
let loopPanel = document.getElementById("loop-notification-panel");
loopPanel.hidePopup();
yield MozLoopService.doNotDisturb = false;
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
- setInternalLoopGlobal("gFxAOAuthProfile", null);
+ MozLoopServiceInternal.fxAOAuthTokenData = null;
yield MozLoopServiceInternal.notifyStatusChanged();
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
});
add_task(function* test_error() {
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
yield MozLoopServiceInternal.setError("testing", {});
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is in error state");
yield MozLoopServiceInternal.clearError("testing");
Assert.notStrictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is not in error state");
});
add_task(function* test_error_with_login() {
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
yield MozLoopServiceInternal.setError("testing", {});
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is in error state");
- setInternalLoopGlobal("gFxAOAuthProfile", {email: "test@example.com", uid: "abcd1234"});
+ MozLoopServiceInternal.fxAOAuthProfile = {email: "test@example.com", uid: "abcd1234"};
MozLoopServiceInternal.notifyStatusChanged("login");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is in error state");
yield MozLoopServiceInternal.clearError("testing");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
- setInternalLoopGlobal("gFxAOAuthProfile", null);
+ MozLoopServiceInternal.fxAOAuthProfile = null;
MozLoopServiceInternal.notifyStatusChanged();
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
});
add_task(function* test_active() {
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
- setInternalLoopGlobal("gFxAOAuthProfile", {email: "test@example.com", uid: "abcd1234"});
+ MozLoopServiceInternal.fxAOAuthTokenData = {token_type:"bearer",access_token:"1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1752",scope:"profile"};
+ MozLoopServiceInternal.fxAOAuthProfile = {email: "test@example.com", uid: "abcd1234"};
yield MozLoopServiceInternal.notifyStatusChanged("login");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "active", "Check button is in active state");
yield loadLoopPanel();
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state after opening panel");
let loopPanel = document.getElementById("loop-notification-panel");
loopPanel.hidePopup();
- setInternalLoopGlobal("gFxAOAuthProfile", null);
+ MozLoopServiceInternal.fxAOAuthTokenData = null;
MozLoopServiceInternal.notifyStatusChanged();
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
});
--- a/browser/components/loop/test/mochitest/head.js
+++ b/browser/components/loop/test/mochitest/head.js
@@ -115,37 +115,36 @@ function promiseOAuthParamsSetup(baseURL
return deferred.promise;
}
function* resetFxA() {
let global = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
global.gHawkClient = null;
global.gFxAOAuthClientPromise = null;
global.gFxAOAuthClient = null;
- global.gFxAOAuthTokenData = null;
- global.gFxAOAuthProfile = null;
+ MozLoopServiceInternal.fxAOAuthProfile = null;
+ MozLoopServiceInternal.fxAOAuthTokenData = null;
const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
Services.prefs.clearUserPref(fxASessionPref);
MozLoopService.errors.clear();
let notified = promiseObserverNotified("loop-status-changed");
MozLoopServiceInternal.notifyStatusChanged();
yield notified;
}
-function setInternalLoopGlobal(aName, aValue) {
- let global = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
- global[aName] = aValue;
+function checkFxAOAuthTokenData(aValue) {
+ ise(MozLoopServiceInternal.fxAOAuthTokenData, aValue, "fxAOAuthTokenData should be " + aValue);
}
function checkLoggedOutState() {
let global = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
ise(global.gFxAOAuthClientPromise, null, "gFxAOAuthClientPromise should be cleared");
- ise(global.gFxAOAuthProfile, null, "gFxAOAuthProfile should be cleared");
+ ise(MozLoopService.userProfile, null, "fxAOAuthProfile should be cleared");
ise(global.gFxAOAuthClient, null, "gFxAOAuthClient should be cleared");
- ise(global.gFxAOAuthTokenData, null, "gFxAOAuthTokenData should be cleared");
+ checkFxAOAuthTokenData(null);
const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
ise(Services.prefs.getPrefType(fxASessionPref), Services.prefs.PREF_INVALID,
"FxA hawk session should be cleared anyways");
}
function promiseDeletedOAuthParams(baseURL) {
let deferred = Promise.defer();
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
--- a/browser/components/loop/test/xpcshell/test_loopservice_busy.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_busy.js
@@ -47,19 +47,20 @@ function test_send_busy_on_call() {
add_test(test_send_busy_on_call); //FXA call accepted, Guest call rejected
add_test(test_send_busy_on_call); //No FXA call, first Guest call accepted, second rejected
function run_test()
{
setupFakeLoopServer();
- // Setup fake login (profile) state so we get FxA requests.
- const serviceGlobal = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
- serviceGlobal.gFxAOAuthProfile = {email: "test@example.com", uid: "abcd1234"};
+ // Setup fake login state so we get FxA requests.
+ const MozLoopServiceInternal = Cu.import("resource:///modules/loop/MozLoopService.jsm", {}).MozLoopServiceInternal;
+ MozLoopServiceInternal.fxAOAuthTokenData = {token_type:"bearer",access_token:"1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1752",scope:"profile"};
+ MozLoopServiceInternal.fxAOAuthProfile = {email: "test@example.com", uid: "abcd1234"};
// For each notification received from the PushServer, MozLoopService will first query
// for any pending calls on the FxA hawk session and then again using the guest session.
// A pair of response objects in the callsResponses array will be consumed for each
// notification. The even calls object is for the FxA session, the odd the Guest session.
let callsRespCount = 0;
let callsResponses = [
@@ -97,16 +98,16 @@ function run_test()
response.finish();
});
do_register_cleanup(function() {
// Revert original Chat.open implementation
Chat.open = openChatOrig;
// Revert fake login state
- serviceGlobal.gFxAOAuthProfile = null;
+ MozLoopServiceInternal.fxAOAuthTokenData = null;
// clear test pref
Services.prefs.clearUserPref("loop.seenToS");
});
run_next_test();
}
--- a/browser/components/loop/test/xpcshell/test_loopservice_initialize.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_initialize.js
@@ -5,18 +5,19 @@ var startTimerCalled = false;
/**
* Tests that registration doesn't happen when the expiry time is
* not set.
*/
add_task(function test_initialize_no_expiry() {
startTimerCalled = false;
- MozLoopService.initialize();
-
+ let initializedPromise = yield MozLoopService.initialize();
+ Assert.equal(initializedPromise, "registration not needed",
+ "Promise should be fulfilled");
Assert.equal(startTimerCalled, false,
"should not register when no expiry time is set");
});
/**
* Tests that registration doesn't happen when the expiry time is
* in the past.
*/
--- a/browser/components/loop/test/xpcshell/test_loopservice_registration.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_registration.js
@@ -35,17 +35,17 @@ add_test(function test_register_offline(
add_test(function test_register_websocket_success_loop_server_fail() {
mockPushHandler.registrationResult = null;
MozLoopService.register(mockPushHandler).then(() => {
do_throw("should not succeed when loop server registration fails");
}, err => {
// 404 is an expected failure indicated by the lack of route being set
// up on the Loop server mock. This is added in the next test.
- Assert.equal(err, 404, "Expected no errors in websocket registration");
+ Assert.equal(err.errno, 404, "Expected no errors in websocket registration");
run_next_test();
});
});
/**
* Tests that we get a success response when both websocket and Loop server
* registration are complete.
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/xpcshell/test_loopservice_restart.js
@@ -0,0 +1,111 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const FAKE_FXA_TOKEN_DATA = JSON.stringify({
+ "token_type": "bearer",
+ "access_token": "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1752",
+ "scope": "profile"
+});
+const FAKE_FXA_PROFILE = JSON.stringify({
+ "email": "test@example.com",
+ "uid": "999999994d9f4b08a2cbfc0999999999",
+ "avatar": null
+});
+const LOOP_FXA_TOKEN_PREF = "loop.fxa_oauth.tokendata";
+const LOOP_FXA_PROFILE_PREF = "loop.fxa_oauth.profile";
+const LOOP_URL_EXPIRY_PREF = "loop.urlsExpiryTimeSeconds";
+const LOOP_INITIAL_DELAY_PREF = "loop.initialDelay";
+
+/**
+ * This file is to test restart+reauth.
+ */
+
+add_task(function test_initialize_with_expired_urls_and_no_auth_token() {
+ // Set time to be 2 seconds in the past.
+ var nowSeconds = Date.now() / 1000;
+ Services.prefs.setIntPref(LOOP_URL_EXPIRY_PREF, nowSeconds - 2);
+ Services.prefs.clearUserPref(LOOP_FXA_TOKEN_PREF);
+
+ yield MozLoopService.initialize(mockPushHandler).then((msg) => {
+ Assert.equal(msg, "registration not needed", "Initialize should not register when the " +
+ "URLs are expired and there are no auth tokens");
+ }, (error) => {
+ Assert.ok(false, error, "should have resolved the promise that initialize returned");
+ });
+});
+
+add_task(function test_initialize_with_urls_and_no_auth_token() {
+ Services.prefs.setIntPref(LOOP_URL_EXPIRY_PREF, Date.now() / 1000 + 10);
+ Services.prefs.clearUserPref(LOOP_FXA_TOKEN_PREF);
+
+ loopServer.registerPathHandler("/registration", (request, response) => {
+ response.setStatusLine(null, 200, "OK");
+ });
+
+ yield MozLoopService.initialize(mockPushHandler).then((msg) => {
+ Assert.equal(msg, "initialized to guest status", "Initialize should register as a " +
+ "guest when no auth tokens but expired URLs");
+ }, (error) => {
+ Assert.ok(false, error, "should have resolved the promise that initialize returned");
+ });
+});
+
+add_task(function test_initialize_with_invalid_fxa_token() {
+ Services.prefs.setCharPref(LOOP_FXA_PROFILE_PREF, FAKE_FXA_PROFILE);
+ Services.prefs.setCharPref(LOOP_FXA_TOKEN_PREF, FAKE_FXA_TOKEN_DATA);
+
+ // Only need to implement the FxA registration because the previous
+ // test registered as a guest.
+ loopServer.registerPathHandler("/registration", (request, response) => {
+ response.setStatusLine(null, 401, "Unauthorized");
+ response.write(JSON.stringify({
+ code: 401,
+ errno: 110,
+ error: "Unauthorized",
+ message: "Unknown credentials",
+ }));
+ });
+
+ yield MozLoopService.initialize(mockPushHandler).then(() => {
+ Assert.ok(false, "Initializing with an invalid token should reject the promise");
+ },
+ (error) => {
+ let pushHandler = Cu.import("resource:///modules/loop/MozLoopService.jsm", {}).gPushHandler;
+ Assert.equal(pushHandler.pushUrl, kEndPointUrl, "Push URL should match");
+ Assert.equal(Services.prefs.getCharPref(LOOP_FXA_TOKEN_PREF), "",
+ "FXA pref should be cleared if token was invalid");
+ Assert.equal(Services.prefs.getCharPref(LOOP_FXA_PROFILE_PREF), "",
+ "FXA profile pref should be cleared if token was invalid");
+ });
+});
+
+add_task(function test_initialize_with_fxa_token() {
+ Services.prefs.setCharPref(LOOP_FXA_PROFILE_PREF, FAKE_FXA_PROFILE);
+ Services.prefs.setCharPref(LOOP_FXA_TOKEN_PREF, FAKE_FXA_TOKEN_DATA);
+ loopServer.registerPathHandler("/registration", (request, response) => {
+ response.setStatusLine(null, 200, "OK");
+ });
+
+ yield MozLoopService.initialize(mockPushHandler).then(() => {
+ Assert.equal(Services.prefs.getCharPref(LOOP_FXA_TOKEN_PREF), FAKE_FXA_TOKEN_DATA,
+ "FXA pref should still be set after initialization");
+ Assert.equal(Services.prefs.getCharPref(LOOP_FXA_PROFILE_PREF), FAKE_FXA_PROFILE,
+ "FXA profile should still be set after initialization");
+ });
+});
+
+function run_test() {
+ setupFakeLoopServer();
+ // Note, this is just used to speed up the test.
+ Services.prefs.setIntPref(LOOP_INITIAL_DELAY_PREF, 0);
+ mockPushHandler.pushUrl = kEndPointUrl;
+
+ do_register_cleanup(function() {
+ Services.prefs.clearUserPref(LOOP_INITIAL_DELAY_PREF);
+ Services.prefs.clearUserPref(LOOP_FXA_TOKEN_PREF);
+ Services.prefs.clearUserPref(LOOP_FXA_PROFILE_PREF);
+ Services.prefs.clearUserPref(LOOP_URL_EXPIRY_PREF);
+ });
+
+ run_next_test();
+};
--- a/browser/components/loop/test/xpcshell/test_loopservice_token_invalid.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_token_invalid.js
@@ -13,21 +13,18 @@ add_test(function test_registration_inva
// Due to the way the time stamp checking code works in hawkclient, we expect a couple
// of authorization requests before we reset the token.
if (request.hasHeader("Authorization")) {
authorizationAttempts++;
response.setStatusLine(null, 401, "Unauthorized");
response.write(JSON.stringify({
code: 401,
errno: 110,
- error: {
- error: "Unauthorized",
- message: "Unknown credentials",
- statusCode: 401
- }
+ error: "Unauthorized",
+ message: "Unknown credentials",
}));
} else {
// We didn't have an authorization header, so check the pref has been cleared.
Assert.equal(Services.prefs.prefHasUserValue(LOOP_HAWK_PREF), false);
response.setStatusLine(null, 200, "OK");
response.setHeader("Hawk-Session-Token", fakeSessionToken2, false);
}
response.processAsync();
--- a/browser/components/loop/test/xpcshell/xpcshell.ini
+++ b/browser/components/loop/test/xpcshell/xpcshell.ini
@@ -10,13 +10,14 @@ skip-if = toolkit == 'gonk'
[test_loopservice_dnd.js]
[test_loopservice_expiry.js]
[test_loopservice_hawk_errors.js]
[test_loopservice_loop_prefs.js]
[test_loopservice_initialize.js]
[test_loopservice_locales.js]
[test_loopservice_notification.js]
[test_loopservice_registration.js]
+[test_loopservice_restart.js]
[test_loopservice_token_invalid.js]
[test_loopservice_token_save.js]
[test_loopservice_token_send.js]
[test_loopservice_token_validation.js]
[test_loopservice_busy.js]
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -738,17 +738,20 @@ BrowserGlue.prototype = {
SignInToWebsiteUX.uninit();
}
#endif
webrtcUI.uninit();
FormValidationHandler.uninit();
// XXX: Temporary hack to allow Loop FxA login after a restart to work.
// Remove this once bug 1071247 is deployed.
- Services.prefs.clearUserPref("loop.hawk-session-token.fxa");
+ if (Services.prefs.getPrefType("loop.autologin-after-restart") != Ci.nsIPrefBranch.PREF_BOOL ||
+ !Services.prefs.getBoolPref("loop.autologin-after-restart")) {
+ Services.prefs.clearUserPref("loop.hawk-session-token.fxa");
+ }
},
// All initial windows have opened.
_onWindowsRestored: function BG__onWindowsRestored() {
// Show update notification, if needed.
if (Services.prefs.prefHasUserValue("app.update.postupdate"))
this._showUpdateNotification();