Bug 978896 - FxA: watch() should get silent assertion if possible. r=ferjm

This commit is contained in:
Jed Parsons 2014-03-11 17:49:26 -07:00
parent aec0138797
commit b38e622a84
9 changed files with 172 additions and 78 deletions

View File

@ -335,6 +335,7 @@ this.DOMIdentity = {
},
_unwatch: function DOMIdentity_unwatch(message, targetMM) {
log("DOMIDentity__unwatch: " + message.id);
this.getService(message).RP.unwatch(message.id, targetMM);
},

View File

@ -670,7 +670,7 @@ nsDOMIdentity.prototype = {
},
uninit: function DOMIdentity_uninit() {
this._log("nsDOMIdentity uninit()");
this._log("nsDOMIdentity uninit() " + this._id);
this._identityInternal._mm.sendAsyncMessage(
"Identity:RP:Unwatch",
{ id: this._id }

View File

@ -41,7 +41,11 @@
onready: onready,
onlogin: onlogin,
onerror: onerror,
onlogout: function() {},
// onlogout will actually be called every time watch() is invoked,
// because fxa will find no signed-in user and so trigger logout.
// For this test, though, we don't care and just ignore logout.
onlogout: function () {},
});
};

View File

@ -34,7 +34,13 @@ is("appStatus" in document.nodePrincipal, true,
function MockFXAManager() {}
MockFXAManager.prototype = {
getAssertion: function(audience) {
getAssertion: function(audience, options) {
// Always reject a request for a silent assertion, simulating the
// scenario in which there is no signed-in user to begin with.
if (options.silent) {
return Promise.resolve(null);
}
let deferred = Promise.defer();
jwcrypto.generateKeyPair("DS160", (err, kp) => {
if (err) {
@ -137,7 +143,7 @@ function receiveMessage(event) {
let expected = app.expected;
is(result.success, expected.success,
"Assertion request " + (expected.success ? "succeeds" : "fails"));
"Assertion request succeeds");
if (expected.success) {
// Confirm that the assertion audience and origin are as expected
@ -180,7 +186,6 @@ window.addEventListener("message", receiveMessage, false, true);
function runTest() {
for (let app of apps) {
dump("** Testing " + app.title + "\n");
// Set up state for message handler
expectedErrors = 0;
receivedErrors = [];

View File

@ -31,7 +31,10 @@ Components.utils.import("resource://gre/modules/identity/FirefoxAccounts.jsm");
// plumbing.
function MockFXAManager() {}
MockFXAManager.prototype = {
getAssertion: function() {
getAssertion: function(audience, options) {
if (options.silent) {
return Promise.resolve(null);
}
return Promise.resolve("here~you.go.dude");
}
};

View File

@ -356,8 +356,18 @@ this.FxAccountsManager = {
);
},
/*
* Try to get an assertion for the given audience.
*
* aOptions can include:
*
* refreshAuthentication - (bool) Force re-auth.
*
* silent - (bool) Prevent any UI interaction.
* I.e., try to get an automatic assertion.
*
*/
getAssertion: function(aAudience, aOptions) {
log.debug("getAssertion " + aAudience + JSON.stringify(aOptions));
if (!aAudience) {
return this._error(ERROR_INVALID_AUDIENCE);
}
@ -390,6 +400,9 @@ this.FxAccountsManager = {
// will return the assertion. Otherwise, we will return an error.
return this._signOut().then(
() => {
if (aOptions.silent) {
return Promise.resolve(null);
}
return this._uiRequest(UI_REQUEST_REFRESH_AUTH,
aAudience, user.accountId);
}
@ -401,6 +414,11 @@ this.FxAccountsManager = {
}
log.debug("No signed in user");
if (aOptions.silent) {
return Promise.resolve(null);
}
// If there is no currently signed in user, we trigger the signIn UI
// flow.
return this._uiRequest(UI_REQUEST_SIGN_IN_FLOW, aAudience);

View File

@ -87,20 +87,38 @@ FxAccountsService.prototype = {
*/
watch: function watch(aRpCaller) {
this._rpFlows.set(aRpCaller.id, aRpCaller);
log.debug("watch: " + aRpCaller.id);
log.debug("Current rp flows: " + this._rpFlows.size);
// Nothing to do but call ready()
// Log the user in, if possible, and then call ready().
let runnable = {
run: () => {
this.fxAccountsManager.getAssertion(aRpCaller.audience, {silent:true}).then(
data => {
if (data) {
this.doLogin(aRpCaller.id, data);
} else {
this.doLogout(aRpCaller.id);
}
this.doReady(aRpCaller.id);
},
error => {
log.error("get silent assertion failed: " + JSON.stringify(error));
this.doError(aRpCaller.id, error.toString());
}
);
}
};
Services.tm.currentThread.dispatch(runnable,
Ci.nsIThread.DISPATCH_NORMAL);
},
unwatch: function(aRpCaller, aTargetMM) {
// nothing to do
/**
* Delete the flow when the screen is unloaded
*/
unwatch: function(aRpCallerId, aTargetMM) {
log.debug("unwatching: " + aRpCallerId);
this._rpFlows.delete(aRpCallerId);
},
/**
@ -160,7 +178,9 @@ FxAccountsService.prototype = {
// Call logout() on the next tick
let runnable = {
run: () => {
this.fxAccountsManager.signOut().then(() => {
this.doLogout(aRpCallerId);
});
}
};
Services.tm.currentThread.dispatch(runnable,

View File

@ -9,7 +9,7 @@ const Cr = Components.results;
Cu.import("resource://testing-common/httpd.js");
// XXX until bug 937114 is fixed
Cu.importGlobalProperties(['atob']);
Cu.importGlobalProperties(["atob"]);
// The following boilerplate makes sure that XPCom calls
// that use the profile directory work.
@ -43,7 +43,7 @@ const TEST_URL2 = "https://myfavoritebaconinacan.com";
const TEST_USER = "user@mozilla.com";
const TEST_PRIVKEY = "fake-privkey";
const TEST_CERT = "fake-cert";
const TEST_ASSERTION = "face-assertion";
const TEST_ASSERTION = "fake-assertion";
const TEST_IDPPARAMS = {
domain: "myfavoriteflan.com",
authentication: "/foo/authenticate.html",
@ -72,8 +72,8 @@ function uuid() {
}
function base64UrlDecode(s) {
s = s.replace(/-/g, '+');
s = s.replace(/_/g, '/');
s = s.replace(/-/g, "+");
s = s.replace(/_/g, "/");
// Replace padding if it was stripped by the sender.
// See http://tools.ietf.org/html/rfc4648#section-4
@ -101,15 +101,15 @@ function mock_doc(aIdentity, aOrigin, aDoFunc) {
mockedDoc.id = uuid();
mockedDoc.loggedInUser = aIdentity;
mockedDoc.origin = aOrigin;
mockedDoc['do'] = aDoFunc;
mockedDoc["do"] = aDoFunc;
mockedDoc._mm = TEST_MESSAGE_MANAGER;
mockedDoc.doReady = partial(aDoFunc, 'ready');
mockedDoc.doLogin = partial(aDoFunc, 'login');
mockedDoc.doLogout = partial(aDoFunc, 'logout');
mockedDoc.doError = partial(aDoFunc, 'error');
mockedDoc.doCancel = partial(aDoFunc, 'cancel');
mockedDoc.doCoffee = partial(aDoFunc, 'coffee');
mockedDoc.childProcessShutdown = partial(aDoFunc, 'child-process-shutdown');
mockedDoc.doReady = partial(aDoFunc, "ready");
mockedDoc.doLogin = partial(aDoFunc, "login");
mockedDoc.doLogout = partial(aDoFunc, "logout");
mockedDoc.doError = partial(aDoFunc, "error");
mockedDoc.doCancel = partial(aDoFunc, "cancel");
mockedDoc.doCoffee = partial(aDoFunc, "coffee");
mockedDoc.childProcessShutdown = partial(aDoFunc, "child-process-shutdown");
mockedDoc.RP = mockedDoc;
@ -127,9 +127,9 @@ function mock_fxa_rp(aIdentity, aOrigin, aDoFunc) {
mockedDoc.doReady = partial(aDoFunc, "ready");
mockedDoc.doLogin = partial(aDoFunc, "login");
mockedDoc.doLogout = partial(aDoFunc, "logout");
mockedDoc.doError = partial(aDoFunc, 'error');
mockedDoc.doCancel = partial(aDoFunc, 'cancel');
mockedDoc.childProcessShutdown = partial(aDoFunc, 'child-process-shutdown');
mockedDoc.doError = partial(aDoFunc, "error");
mockedDoc.doCancel = partial(aDoFunc, "cancel");
mockedDoc.childProcessShutdown = partial(aDoFunc, "child-process-shutdown");
mockedDoc.RP = mockedDoc;

View File

@ -14,13 +14,19 @@ XPCOMUtils.defineLazyModuleGetter(this, "FirefoxAccounts",
// data.
do_get_profile();
function MockFXAManager() {}
function MockFXAManager() {
this.signedIn = true;
}
MockFXAManager.prototype = {
getAssertion: function(audience) {
let deferred = Promise.defer();
deferred.resolve(TEST_ASSERTION);
return deferred.promise;
}
let result = this.signedIn ? TEST_ASSERTION : null;
return Promise.resolve(result);
},
signOut: function() {
this.signedIn = false;
return Promise.resolve(null);
},
}
let originalManager = FirefoxAccounts.fxAccountsManager;
@ -45,13 +51,49 @@ function test_mock() {
});
}
function test_watch() {
function test_watch_signed_in() {
do_test_pending();
let mockedRP = mock_fxa_rp(null, TEST_URL, function(method) {
do_check_eq(method, "ready");
let received = [];
let mockedRP = mock_fxa_rp(null, TEST_URL, function(method, data) {
received.push([method, data]);
if (method == "ready") {
// confirm that we were signed in and then ready was called
do_check_eq(received.length, 2);
do_check_eq(received[0][0], "login");
do_check_eq(received[0][1], TEST_ASSERTION);
do_check_eq(received[1][0], "ready");
do_test_finished();
run_next_test();
}
});
FirefoxAccounts.RP.watch(mockedRP);
}
function test_watch_signed_out() {
do_test_pending();
let received = [];
let signedInState = FirefoxAccounts.fxAccountsManager.signedIn;
FirefoxAccounts.fxAccountsManager.signedIn = false;
let mockedRP = mock_fxa_rp(null, TEST_URL, function(method) {
received.push(method);
if (method == "ready") {
// confirm that we were signed out and then ready was called
do_check_eq(received.length, 2);
do_check_eq(received[0], "logout");
do_check_eq(received[1], "ready");
// restore initial state
FirefoxAccounts.fxAccountsManager.signedIn = signedInState;
do_test_finished();
run_next_test();
}
});
FirefoxAccounts.RP.watch(mockedRP);
@ -62,21 +104,31 @@ function test_request() {
let received = [];
let mockedRP = mock_fxa_rp(null, TEST_URL, function(method) {
// We will received "ready" as a result of watch(), then "login"
// as a result of request()
received.push(method);
// initially signed out
let signedInState = FirefoxAccounts.fxAccountsManager.signedIn;
FirefoxAccounts.fxAccountsManager.signedIn = false;
if (received.length == 2) {
do_check_eq(received[0], "ready");
do_check_eq(received[1], "login");
do_test_finished();
run_next_test();
let mockedRP = mock_fxa_rp(null, TEST_URL, function(method, data) {
received.push([method, data]);
// On watch(), we are signed out. Then we call request().
if (received.length === 2) {
do_check_eq(received[0][0], "logout");
do_check_eq(received[1][0], "ready");
// Pretend request() showed ux and the user signed in
FirefoxAccounts.fxAccountsManager.signedIn = true;
FirefoxAccounts.RP.request(mockedRP.id);
}
// Second, call request()
if (method == "ready") {
FirefoxAccounts.RP.request(mockedRP.id);
if (received.length === 3) {
do_check_eq(received[2][0], "login");
do_check_eq(received[2][1], TEST_ASSERTION);
// restore initial state
FirefoxAccounts.fxAccountsManager.signedIn = signedInState;
do_test_finished();
run_next_test();
}
});
@ -90,20 +142,20 @@ function test_logout() {
let received = [];
let mockedRP = mock_fxa_rp(null, TEST_URL, function(method) {
// We will receive "ready" as a result of watch(), and "logout"
// as a result of logout()
received.push(method);
if (received.length == 2) {
do_check_eq(received[0], "ready");
do_check_eq(received[1], "logout");
do_test_finished();
run_next_test();
// At first, watch() signs us in automatically. Then we sign out.
if (received.length === 2) {
do_check_eq(received[0], "login");
do_check_eq(received[1], "ready");
FirefoxAccounts.RP.logout(mockedRP.id);
}
if (method == "ready") {
// Second, call logout()
FirefoxAccounts.RP.logout(mockedRP.id);
if (received.length === 3) {
do_check_eq(received[2], "logout");
do_test_finished();
run_next_test();
}
});
@ -122,31 +174,21 @@ function test_error() {
let originalManager = FirefoxAccounts.fxAccountsManager;
FirefoxAccounts.RP.fxAccountsManager = {
getAssertion: function(audience) {
return Promise.reject("barf!");
return Promise.reject(new Error("barf!"));
}
};
let mockedRP = mock_fxa_rp(null, TEST_URL, function(method, message) {
// We will receive "ready" as a result of watch(), and "logout"
// as a result of logout()
received.push([method, message]);
if (received.length == 2) {
do_check_eq(received[0][0], "ready");
do_check_eq(received[1][0], "error");
do_check_eq(received[1][1], "barf!");
// We will immediately receive an error, due to watch()'s attempt
// to getAssertion().
do_check_eq(method, "error");
do_check_true(/barf/.test(message));
// Put things back the way they were
FirefoxAccounts.fxAccountsManager = originalManager;
do_test_finished();
run_next_test();
}
if (method == "ready") {
FirefoxAccounts.RP.request(mockedRP.id);
}
});
// First, call watch()
@ -197,7 +239,8 @@ function test_child_process_shutdown() {
let TESTS = [
test_overall,
test_mock,
test_watch,
test_watch_signed_in,
test_watch_signed_out,
test_request,
test_logout,
test_error,