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) { _unwatch: function DOMIdentity_unwatch(message, targetMM) {
log("DOMIDentity__unwatch: " + message.id);
this.getService(message).RP.unwatch(message.id, targetMM); this.getService(message).RP.unwatch(message.id, targetMM);
}, },

View File

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

View File

@ -41,7 +41,11 @@
onready: onready, onready: onready,
onlogin: onlogin, onlogin: onlogin,
onerror: onerror, 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() {} function MockFXAManager() {}
MockFXAManager.prototype = { 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(); let deferred = Promise.defer();
jwcrypto.generateKeyPair("DS160", (err, kp) => { jwcrypto.generateKeyPair("DS160", (err, kp) => {
if (err) { if (err) {
@ -137,7 +143,7 @@ function receiveMessage(event) {
let expected = app.expected; let expected = app.expected;
is(result.success, expected.success, is(result.success, expected.success,
"Assertion request " + (expected.success ? "succeeds" : "fails")); "Assertion request succeeds");
if (expected.success) { if (expected.success) {
// Confirm that the assertion audience and origin are as expected // Confirm that the assertion audience and origin are as expected
@ -180,7 +186,6 @@ window.addEventListener("message", receiveMessage, false, true);
function runTest() { function runTest() {
for (let app of apps) { for (let app of apps) {
dump("** Testing " + app.title + "\n"); dump("** Testing " + app.title + "\n");
// Set up state for message handler // Set up state for message handler
expectedErrors = 0; expectedErrors = 0;
receivedErrors = []; receivedErrors = [];

View File

@ -31,7 +31,10 @@ Components.utils.import("resource://gre/modules/identity/FirefoxAccounts.jsm");
// plumbing. // plumbing.
function MockFXAManager() {} function MockFXAManager() {}
MockFXAManager.prototype = { MockFXAManager.prototype = {
getAssertion: function() { getAssertion: function(audience, options) {
if (options.silent) {
return Promise.resolve(null);
}
return Promise.resolve("here~you.go.dude"); 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) { getAssertion: function(aAudience, aOptions) {
log.debug("getAssertion " + aAudience + JSON.stringify(aOptions));
if (!aAudience) { if (!aAudience) {
return this._error(ERROR_INVALID_AUDIENCE); return this._error(ERROR_INVALID_AUDIENCE);
} }
@ -390,6 +400,9 @@ this.FxAccountsManager = {
// will return the assertion. Otherwise, we will return an error. // will return the assertion. Otherwise, we will return an error.
return this._signOut().then( return this._signOut().then(
() => { () => {
if (aOptions.silent) {
return Promise.resolve(null);
}
return this._uiRequest(UI_REQUEST_REFRESH_AUTH, return this._uiRequest(UI_REQUEST_REFRESH_AUTH,
aAudience, user.accountId); aAudience, user.accountId);
} }
@ -401,6 +414,11 @@ this.FxAccountsManager = {
} }
log.debug("No signed in user"); 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 // If there is no currently signed in user, we trigger the signIn UI
// flow. // flow.
return this._uiRequest(UI_REQUEST_SIGN_IN_FLOW, aAudience); return this._uiRequest(UI_REQUEST_SIGN_IN_FLOW, aAudience);

View File

@ -87,20 +87,38 @@ FxAccountsService.prototype = {
*/ */
watch: function watch(aRpCaller) { watch: function watch(aRpCaller) {
this._rpFlows.set(aRpCaller.id, aRpCaller); this._rpFlows.set(aRpCaller.id, aRpCaller);
log.debug("watch: " + aRpCaller.id);
log.debug("Current rp flows: " + this._rpFlows.size); 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 = { let runnable = {
run: () => { run: () => {
this.doReady(aRpCaller.id); 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, Services.tm.currentThread.dispatch(runnable,
Ci.nsIThread.DISPATCH_NORMAL); 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 // Call logout() on the next tick
let runnable = { let runnable = {
run: () => { run: () => {
this.doLogout(aRpCallerId); this.fxAccountsManager.signOut().then(() => {
this.doLogout(aRpCallerId);
});
} }
}; };
Services.tm.currentThread.dispatch(runnable, Services.tm.currentThread.dispatch(runnable,

View File

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

View File

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