mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-21 01:05:45 +00:00
Bug 1047667 - Unregister logged in user from the Loop server upon logout. r=jaws
This commit is contained in:
parent
44294c5842
commit
592c049281
@ -426,6 +426,14 @@ function injectLoopAPI(targetWindow) {
|
||||
}
|
||||
},
|
||||
|
||||
logOutFromFxA: {
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
value: function() {
|
||||
return MozLoopService.logOutFromFxA();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Copies passed string onto the system clipboard.
|
||||
*
|
||||
|
@ -73,7 +73,6 @@ XPCOMUtils.defineLazyServiceGetter(this, "gDNSService",
|
||||
let gRegisteredDeferred = null;
|
||||
let gPushHandler = null;
|
||||
let gHawkClient = null;
|
||||
let gRegisteredLoopServer = false;
|
||||
let gLocalizedStrings = null;
|
||||
let gInitializeTimer = null;
|
||||
let gFxAOAuthClientPromise = null;
|
||||
@ -292,6 +291,20 @@ let MozLoopServiceInternal = {
|
||||
return true;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Clear the loop session token so we don't use it for Hawk Requests anymore.
|
||||
*
|
||||
* This should normally be used after unregistering with the server so it can
|
||||
* clean up session state first.
|
||||
*
|
||||
* @param {LOOP_SESSION_TYPE} sessionType The type of session to use for the request.
|
||||
* One of the LOOP_SESSION_TYPE members.
|
||||
*/
|
||||
clearSessionToken: function(sessionType) {
|
||||
Services.prefs.clearUserPref(this.getSessionTokenPrefName(sessionType));
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback from MozLoopPushHandler - The push server has been registered
|
||||
* and has given us a push url.
|
||||
@ -314,7 +327,7 @@ let MozLoopServiceInternal = {
|
||||
// No need to clear the promise here, everything was good, so we don't need
|
||||
// to re-register.
|
||||
}, (error) => {
|
||||
Cu.reportError("Failed to register with Loop server: " + error.errno);
|
||||
console.error("Failed to register with Loop server: ", error);
|
||||
gRegisteredDeferred.reject(error.errno);
|
||||
gRegisteredDeferred = null;
|
||||
});
|
||||
@ -349,20 +362,50 @@ let MozLoopServiceInternal = {
|
||||
}
|
||||
|
||||
// Authorization failed, invalid token, we need to try again with a new token.
|
||||
Services.prefs.clearUserPref(this.getSessionTokenPrefName(sessionType));
|
||||
this.clearSessionToken(sessionType);
|
||||
if (retry) {
|
||||
return this.registerWithLoopServer(sessionType, pushUrl, false);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX Bubble the precise details up to the UI somehow (bug 1013248).
|
||||
Cu.reportError("Failed to register with the loop server. error: " + error);
|
||||
console.error("Failed to register with the loop server. Error: ", error);
|
||||
this.setError("registration", error);
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unregisters from the Loop server either as a guest or a FxA user.
|
||||
*
|
||||
* This is normally only wanted for FxA users as we normally want to keep the
|
||||
* guest session with the device.
|
||||
*
|
||||
* @param {LOOP_SESSION_TYPE} sessionType The type of session e.g. guest or FxA
|
||||
* @param {String} pushURL The push URL previously given by the push server.
|
||||
* This may not be necessary to unregister in the future.
|
||||
* @return {Promise} resolving when the unregistration request finishes
|
||||
*/
|
||||
unregisterFromLoopServer: function(sessionType, pushURL) {
|
||||
let unregisterURL = "/registration?simplePushURL=" + encodeURIComponent(pushURL);
|
||||
return this.hawkRequest(sessionType, unregisterURL, "DELETE")
|
||||
.then(() => {
|
||||
MozLoopServiceInternal.clearSessionToken(sessionType);
|
||||
},
|
||||
error => {
|
||||
// Always clear the registration token regardless of whether the server acknowledges the logout.
|
||||
MozLoopServiceInternal.clearSessionToken(sessionType);
|
||||
if (error.code === 401 && error.errno === INVALID_AUTH_TOKEN) {
|
||||
// Authorization failed, invalid token. This is fine since it may mean we already logged out.
|
||||
return;
|
||||
}
|
||||
|
||||
console.error("Failed to unregister with the loop server. Error: ", error);
|
||||
throw error;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback from MozLoopPushHandler - A push notification has been received from
|
||||
* the server.
|
||||
@ -1038,6 +1081,30 @@ this.MozLoopService = {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Logs the user out from FxA.
|
||||
*
|
||||
* Gracefully handles if the user is already logged out.
|
||||
*
|
||||
* @return {Promise} that resolves when the FxA logout flow is complete.
|
||||
*/
|
||||
logOutFromFxA: Task.async(function*() {
|
||||
yield MozLoopServiceInternal.unregisterFromLoopServer(LOOP_SESSION_TYPE.FXA,
|
||||
gPushHandler.pushUrl);
|
||||
|
||||
gFxAOAuthTokenData = null;
|
||||
gFxAOAuthProfile = 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.
|
||||
MozLoopServiceInternal.clearError("registration");
|
||||
}),
|
||||
|
||||
/**
|
||||
* Performs a hawk based request to the loop server.
|
||||
*
|
||||
|
@ -221,7 +221,6 @@ loop.panel = (function(_, mozL10n) {
|
||||
|
||||
handleClickAuthEntry: function() {
|
||||
if (this._isSignedIn()) {
|
||||
// XXX to be implemented - bug 979845
|
||||
navigator.mozLoop.logOutFromFxA();
|
||||
} else {
|
||||
navigator.mozLoop.logInToFxA();
|
||||
|
@ -221,7 +221,6 @@ loop.panel = (function(_, mozL10n) {
|
||||
|
||||
handleClickAuthEntry: function() {
|
||||
if (this._isSignedIn()) {
|
||||
// XXX to be implemented - bug 979845
|
||||
navigator.mozLoop.logOutFromFxA();
|
||||
} else {
|
||||
navigator.mozLoop.logInToFxA();
|
||||
|
@ -250,7 +250,8 @@ add_task(function* basicAuthorizationAndRegistration() {
|
||||
is(loopButton.getAttribute("state"), "active", "state of loop button should be active when logged in");
|
||||
|
||||
let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
|
||||
ise(registrationResponse.response.simplePushURL, "https://localhost/pushUrl/fxa", "Check registered push URL");
|
||||
ise(registrationResponse.response.simplePushURL, "https://localhost/pushUrl/fxa",
|
||||
"Check registered push URL");
|
||||
|
||||
let loopPanel = document.getElementById("loop-notification-panel");
|
||||
loopPanel.hidePopup();
|
||||
@ -259,6 +260,15 @@ add_task(function* basicAuthorizationAndRegistration() {
|
||||
yield statusChangedPromise;
|
||||
is(loopButton.getAttribute("state"), "", "state of loop button should return to empty after panel is opened");
|
||||
loopPanel.hidePopup();
|
||||
|
||||
info("logout");
|
||||
yield MozLoopService.logOutFromFxA();
|
||||
checkLoggedOutState();
|
||||
registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
|
||||
ise(registrationResponse.response, null,
|
||||
"Check registration was deleted on the server");
|
||||
is(visibleEmail.textContent, "Guest", "Guest should be displayed on the panel again after logout");
|
||||
is(MozLoopService.userProfile, null, "userProfile should be null after logout");
|
||||
});
|
||||
|
||||
add_task(function* loginWithParams401() {
|
||||
@ -284,6 +294,52 @@ add_task(function* loginWithParams401() {
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* logoutWithIncorrectPushURL() {
|
||||
resetFxA();
|
||||
let pushURL = "http://www.example.com/";
|
||||
mockPushHandler.pushUrl = pushURL;
|
||||
|
||||
// Create a fake FxA hawk session token
|
||||
const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
|
||||
Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH));
|
||||
|
||||
yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, pushURL);
|
||||
let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
|
||||
ise(registrationResponse.response.simplePushURL, pushURL, "Check registered push URL");
|
||||
mockPushHandler.pushUrl = "http://www.example.com/invalid";
|
||||
let caught = false;
|
||||
yield MozLoopService.logOutFromFxA().catch((error) => {
|
||||
caught = true;
|
||||
});
|
||||
ok(caught, "Should have caught an error logging out with a mismatched push URL");
|
||||
checkLoggedOutState();
|
||||
registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
|
||||
ise(registrationResponse.response.simplePushURL, pushURL, "Check registered push URL wasn't deleted");
|
||||
});
|
||||
|
||||
add_task(function* logoutWithNoPushURL() {
|
||||
resetFxA();
|
||||
let pushURL = "http://www.example.com/";
|
||||
mockPushHandler.pushUrl = pushURL;
|
||||
|
||||
// Create a fake FxA hawk session token
|
||||
const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
|
||||
Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH));
|
||||
|
||||
yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, pushURL);
|
||||
let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
|
||||
ise(registrationResponse.response.simplePushURL, pushURL, "Check registered push URL");
|
||||
mockPushHandler.pushUrl = null;
|
||||
let caught = false;
|
||||
yield MozLoopService.logOutFromFxA().catch((error) => {
|
||||
caught = true;
|
||||
});
|
||||
ok(caught, "Should have caught an error logging out without a push URL");
|
||||
checkLoggedOutState();
|
||||
registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
|
||||
ise(registrationResponse.response.simplePushURL, pushURL, "Check registered push URL wasn't deleted");
|
||||
});
|
||||
|
||||
add_task(function* loginWithRegistration401() {
|
||||
resetFxA();
|
||||
let params = {
|
||||
|
@ -123,6 +123,17 @@ function setInternalLoopGlobal(aName, aValue) {
|
||||
global[aName] = 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(global.gFxAOAuthClient, null, "gFxAOAuthClient should be cleared");
|
||||
ise(global.gFxAOAuthTokenData, null, "gFxAOAuthTokenData should be cleared");
|
||||
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"].
|
||||
|
@ -11,28 +11,34 @@ const REQUIRED_PARAMS = ["client_id", "content_uri", "oauth_uri", "profile_uri",
|
||||
const HAWK_TOKEN_LENGTH = 64;
|
||||
|
||||
Components.utils.import("resource://gre/modules/NetUtil.jsm");
|
||||
Components.utils.importGlobalProperties(["URL"]);
|
||||
|
||||
/**
|
||||
* Entry point for HTTP requests.
|
||||
*/
|
||||
function handleRequest(request, response) {
|
||||
// Look at the query string but ignore past the encoded ? when deciding on the handler.
|
||||
dump("loop_fxa.sjs request for: " + request.queryString + "\n");
|
||||
switch (request.queryString.replace(/%3F.*/,"")) {
|
||||
// Convert the query string to a path with a placeholder base of example.com
|
||||
let url = new URL(request.queryString.replace(/%3F.*/,""), "http://www.example.com");
|
||||
dump("loop_fxa.sjs request for: " + url.pathname + "\n");
|
||||
switch (url.pathname) {
|
||||
case "/setup_params": // Test-only
|
||||
setup_params(request, response);
|
||||
return;
|
||||
case "/fxa-oauth/params":
|
||||
params(request, response);
|
||||
return;
|
||||
case encodeURIComponent("/oauth/authorization"):
|
||||
case "/" + encodeURIComponent("/oauth/authorization"):
|
||||
oauth_authorization(request, response);
|
||||
return;
|
||||
case "/fxa-oauth/token":
|
||||
token(request, response);
|
||||
return;
|
||||
case "/registration":
|
||||
registration(request, response);
|
||||
if (request.method == "DELETE") {
|
||||
delete_registration(request, response);
|
||||
} else {
|
||||
registration(request, response);
|
||||
}
|
||||
return;
|
||||
case "/get_registration": // Test-only
|
||||
get_registration(request, response);
|
||||
@ -201,6 +207,31 @@ function registration(request, response) {
|
||||
setSharedState("/registration", body);
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /registration
|
||||
*
|
||||
* Hawk Authorization headers are required.
|
||||
*/
|
||||
function delete_registration(request, response) {
|
||||
if (!request.hasHeader("Authorization") ||
|
||||
!request.getHeader("Authorization").startsWith("Hawk")) {
|
||||
response.setStatusLine(request.httpVersion, 401, "Missing Hawk");
|
||||
response.write("401 Missing Hawk Authorization header");
|
||||
return;
|
||||
}
|
||||
|
||||
// Do some query string munging due to the SJS file using a base with a trailing "?"
|
||||
// making the path become a query parameter. This is because we aren't actually
|
||||
// registering endpoints at the root of the hostname e.g. /registration.
|
||||
let url = new URL(request.queryString.replace(/%3F.*/,""), "http://www.example.com");
|
||||
let registration = JSON.parse(getSharedState("/registration"));
|
||||
if (registration.simplePushURL == url.searchParams.get("simplePushURL")) {
|
||||
setSharedState("/registration", "");
|
||||
} else {
|
||||
response.setStatusLine(request.httpVersion, 400, "Bad Request");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /get_registration
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user