diff --git a/resources/static/dialog/controllers/required_email.js b/resources/static/dialog/controllers/required_email.js index c3f2bd2d982a2afc8faa459096aff52b5bcd0512..84cc4e18b592359661eec599de20ec159ab16dc2 100644 --- a/resources/static/dialog/controllers/required_email.js +++ b/resources/static/dialog/controllers/required_email.js @@ -50,21 +50,32 @@ BrowserID.Modules.RequiredEmail = (function() { authenticated, primaryInfo; + function closePrimaryUser(callback) { + this.close("primary_user", _.extend(primaryInfo, { + email: email, + requiredEmail: true, + add: authenticated + })); + + callback && callback(); + } + function signIn(callback) { var self = this; // If the user is already authenticated and they own this address, sign // them in. if (authenticated) { - dialogHelpers.getAssertion.call(self, email, callback); + if(primaryInfo) { + closePrimaryUser.call(self, callback); + } + else { + dialogHelpers.getAssertion.call(self, email, callback); + } } else { if(primaryInfo) { - self.close("primary_user", _.extend(primaryInfo, { - email: email, - requiredEmail: true - })); - callback && callback(); + closePrimaryUser.call(self, callback); } else { // If the user is not already authenticated, but they potentially own @@ -78,7 +89,7 @@ BrowserID.Modules.RequiredEmail = (function() { // assertion for the email we care about. user.syncEmailKeypair(email, function() { dialogHelpers.getAssertion.call(self, email, callback); - }, self.getErrorDialog(errors.syncEmailKeypair)); + }, self.getErrorDialog(errors.syncEmailKeypair, callback)); } else { callback && callback(); @@ -143,12 +154,25 @@ BrowserID.Modules.RequiredEmail = (function() { if(emailInfo.type === "secondary" || emailInfo.cert) { // secondary user or cert is valid, user can sign in normally. showTemplate({ signin: true }); + ready(); } else { - // Uh oh, certificate is expired, take care of that. - self.close("primary_user", { email: email, requiredEmail: true }); + // Uh oh, this is a primary user whose certificate is expired, take care of that. + user.addressInfo(email, function(info) { + primaryInfo = info; + if (info.authed) { + // If the user is authed with the IdP, give them the opportunity to press + // "signin" before passing them off to the primary user flow + showTemplate({ signin: true }); + } + else { + // User is not authed with IdP, start the verification flow, + // add address to current user. + closePrimaryUser.call(self); + } + ready(); + }, self.getErrorDialog(errors.addressInfo, ready)); } - ready(); } else { // User does not control address, time to verify. @@ -156,36 +180,39 @@ BrowserID.Modules.RequiredEmail = (function() { // authenticated user who does not own primary address, make them // verify it. if(info.type === "primary") { - self.close("primary_user", - _.extend(info, { email: email, requiredEmail: true })); + primaryInfo = info; + if (info.authed) { + // If the user is authed with the IdP, give them the opportunity to press + // "signin" before passing them off to the primary user flow + showTemplate({ signin: true }); + } + else { + // If user is not authed with IdP, kick them through the + // primary_user flow to get them verified. + closePrimaryUser.call(self); + } } else { showTemplate({ verify: true }); } ready(); - }); + }, self.getErrorDialog(errors.addressInfo, ready)); } } else { user.addressInfo(email, function(info) { if (info.type === "primary") { - user.isUserAuthenticatedToPrimary(email, info, function(authed) { - primaryInfo = info; - if (authed) { - // If the user is authenticated with their IdP, show the - // sign in button to give the user the chance to abort. - showTemplate({ signin: true, primary: true }); - } - else { - // If the user is not authenticated with their IdP, pass them - // off to the primary user flow. - self.close("primary_user", { - email: email, - auth_url: primaryInfo.auth - }); - } - ready(); - }, self.getErrorDialog(errors.addressInfo, ready)); + primaryInfo = info; + if (info.authed) { + // If the user is authenticated with their IdP, show the + // sign in button to give the user the chance to abort. + showTemplate({ signin: true, primary: true }); + } + else { + // If the user is not authenticated with their IdP, pass them + // off to the primary user flow. + closePrimaryUser.call(self); + } } else { // If the current email address is registered but the user is not @@ -197,8 +224,8 @@ BrowserID.Modules.RequiredEmail = (function() { else { showTemplate({ verify: true }); } - ready(); } + ready(); }, self.getErrorDialog(errors.addressInfo, ready)); } diff --git a/resources/static/dialog/resources/state_machine.js b/resources/static/dialog/resources/state_machine.js index a0b0e3f3c107810a92eb43183f471a33f90b71ff..e4b86b772e3eb3e3718f23f708731ba393dfe30f 100644 --- a/resources/static/dialog/resources/state_machine.js +++ b/resources/static/dialog/resources/state_machine.js @@ -216,13 +216,12 @@ subscribe("email_chosen", function(msg, info) { var idInfo = storage.getEmail(info.email); if(idInfo) { - if(idInfo.type === "primary") { - // If the email is a primary, throw the user down the primary flow. + if(idInfo.type === "primary" && !idInfo.cert) { + // If the email is a primary, and their cert is not available, + // throw the user down the primary flow. // Doing so will catch cases where the primary certificate is expired - // and the user must re-verify with their IdP. This flow will - // generate its own assertion when ready. For efficiency, we could - // check here whether the cert is ready, but it is early days yet and - // the format may change. + // and the user must re-verify with their IdP. This flow will + // generate its own assertion when ready. publish("primary_user", info); } else { diff --git a/resources/static/shared/error-messages.js b/resources/static/shared/error-messages.js index d72a076ddaaa6834758b61c0a2e4d3dbcb30c649..dec64b4ea3478b4566c754f82e68d895cc1f048c 100644 --- a/resources/static/shared/error-messages.js +++ b/resources/static/shared/error-messages.js @@ -89,6 +89,10 @@ BrowserID.Errors = (function(){ title: "Checking Email Address" }, + isUserAuthenticatedToPrimary: { + title: "Checking Whether User is Authenticated with IdP" + }, + logoutUser: { title: "Logout Failed" }, diff --git a/resources/static/shared/user.js b/resources/static/shared/user.js index a16b17b879a58c5d5a1f0f2b384c451bc81d4cef..95c714253f952268603e94c20898fdff1e832c0b 100644 --- a/resources/static/shared/user.js +++ b/resources/static/shared/user.js @@ -43,7 +43,8 @@ BrowserID.User = (function() { network = bid.Network, storage = bid.Storage, User, pollTimeout, - provisioning = bid.Provisioning; + provisioning = bid.Provisioning, + addressCache = {}; function prepareDeps() { if (!jwk) { @@ -235,6 +236,7 @@ BrowserID.User = (function() { reset: function() { provisioning = BrowserID.Provisioning; + addressCache = {}; }, /** @@ -311,7 +313,7 @@ BrowserID.User = (function() { createUser: function(email, onComplete, onFailure) { var self=this; - network.addressInfo(email, function(info) { + User.addressInfo(email, function(info) { User.createUserWithInfo(email, info, onComplete, onFailure); }, onFailure); }, @@ -750,7 +752,27 @@ BrowserID.User = (function() { * @param {function} [onFailure] - Called on XHR failure. */ addressInfo: function(email, onSuccess, onFailure) { - network.addressInfo(email, onSuccess, onFailure); + function success(info) { + addressCache[email] = info; + onSuccess && onSuccess(info); + } + + if(addressCache[email]) { + success(addressCache[email]); + } + else { + network.addressInfo(email, function(info) { + if(info.type === "primary") { + User.isUserAuthenticatedToPrimary(email, info, function(authed) { + info.authed = authed; + success(info); + }, onFailure); + } + else { + success(info); + } + }, onFailure); + } }, /** @@ -927,7 +949,7 @@ BrowserID.User = (function() { // first we have to get the address info, then attempt // a provision, then if the user is provisioned, go and get an // assertion. - network.addressInfo(email, function(info) { + User.addressInfo(email, function(info) { User.provisionPrimaryUser(email, info, function(status) { if (status === "primary.verified") { User.getAssertion(email, audience, onComplete, onFailure); diff --git a/resources/static/test/index.html b/resources/static/test/index.html index 23594e3446db5b3508fd8afd5c96f28c1e16cf75..bd29fab45834c8ea10c39a8375c131259b2ea75d 100644 --- a/resources/static/test/index.html +++ b/resources/static/test/index.html @@ -59,7 +59,7 @@ <script type="text/javascript" src="/lib/underscore-min.js"></script> <script type="text/javascript" src="/lib/ejs.js"></script> <script type="text/javascript" src="/lib/vepbundle.js"></script> - <script type="text/javascript" src="http://testmob.org/scripts/reporter.js"></script> + <!--script type="text/javascript" src="http://testmob.org/scripts/reporter.js"></script--> <script type="text/javascript" src="/shared/browserid.js"></script> <script type="text/javascript" src="/lib/dom-jquery.js"></script> <script type="text/javascript" src="/lib/hub.js"></script> diff --git a/resources/static/test/qunit/controllers/required_email_unit_test.js b/resources/static/test/qunit/controllers/required_email_unit_test.js index 9dede342aa9964d56b498208f1523d2e37093ab2..e806045deacf239d1d77316c79e478c89642f972 100644 --- a/resources/static/test/qunit/controllers/required_email_unit_test.js +++ b/resources/static/test/qunit/controllers/required_email_unit_test.js @@ -44,16 +44,27 @@ storage = bid.Storage, testHelpers = bid.TestHelpers, register = testHelpers.register, - provisioning = bid.Mocks.Provisioning; + provisioning = bid.Mocks.Provisioning, + origStart; module("controllers/required_email", { setup: function() { + origStart = start; + var count = 0; + start = function() { + if(count) { + throw "multiple starts in a test"; + } + count++; + origStart(); + }; testHelpers.setup(); $("#required_email").text(""); }, teardown: function() { + start = origStart; if (controller) { try { controller.destroy(); @@ -71,11 +82,19 @@ controller.start(options); } + function testPasswordSection() { + equal($("#password_section").length, 1, "password section is there"); + } + + function testNoPasswordSection() { + equal($("#password_section").length, 0, "password section is not there"); + } + function testSignIn(email, cb) { var el = $("#required_email"); equal(el.val() || el.text(), email, "email set correctly"); equal($("#sign_in").length, 1, "sign in button shown"); - equal($("#verify_address").length, 0, "verify address not shows"); + equal($("#verify_address").length, 0, "verify address not shown"); cb && cb(); start(); } @@ -90,14 +109,6 @@ start(); } - function testPasswordSection() { - equal($("#password_section").length, 1, "password section is there"); - } - - function testNoPasswordSection() { - equal($("#password_section").length, 0, "password section is not there"); - } - asyncTest("known_secondary: user who is not authenticated - show password form", function() { var email = "registered@testuser.com"; xhr.useResult("known_secondary"); @@ -109,7 +120,6 @@ testSignIn(email, testPasswordSection); } }); - }); asyncTest("unknown_secondary: user who is not authenticated - user must verify", function() { @@ -129,6 +139,8 @@ var email = "testuser@testuser.com"; storage.addEmail(email, { type: "primary", cert: "cert" }); + xhr.useResult("primary"); + createController({ email: email, authenticated: true, @@ -136,13 +148,31 @@ testSignIn(email); } }); + }); + + asyncTest("primary: user who is authenticated, owns address, cert expired or invalid, authed with IdP - sees signin screen", function() { + var email = "registered@testuser.com", + msgInfo; + xhr.useResult("primary"); + provisioning.setStatus(provisioning.AUTHENTICATED); + storage.addEmail(email, { type: "primary" }); + + createController({ + email: email, + authenticated: true, + ready: function() { + testSignIn(email); + } + }); }); - asyncTest("primary: user who is authenticated, owns address, cert expired or invalid - redirected to 'primary_user'", function() { + asyncTest("primary: user who is authenticated, owns address, cert expired or invalid, not authed with IdP - redirected to 'primary_user'", function() { var email = "registered@testuser.com", msgInfo; + xhr.useResult("primary"); + provisioning.setStatus(provisioning.NOT_AUTHENTICATED); storage.addEmail(email, { type: "primary" }); register("primary_user", function(msg, info) { @@ -159,7 +189,23 @@ }); }); - asyncTest("primary: user who is authenticated, does not own address - redirected to 'primary_user'", function() { + asyncTest("primary: user who is authenticated, does not own address, authed with IdP - user sees signin screen", function() { + var email = "unregistered@testuser.com", + msgInfo; + + xhr.useResult("primary"); + provisioning.setStatus(provisioning.AUTHENTICATED); + + createController({ + email: email, + authenticated: true, + ready: function() { + testSignIn(email); + } + }); + }); + + asyncTest("primary: user who is authenticated, does not own address, not authed with IdP - redirected to 'primary_user'", function() { var email = "unregistered@testuser.com", msgInfo; @@ -294,12 +340,15 @@ authenticated: true }); + var assertion; register("assertion_generated", function(item, info) { - ok(info.assertion, "we have an assertion"); - start(); + assertion = info.assertion; }); - controller.signIn(); + controller.signIn(function() { + ok(assertion, "we have an assertion"); + start(); + }); }); }); @@ -315,15 +364,18 @@ email: email, authenticated: false, ready: function() { + var assertion; register("assertion_generated", function(item, info) { - ok(info.assertion, "we have an assertion"); - start(); + assertion = info.assertion; }); xhr.useResult("valid"); $("#password").val("password"); - controller.signIn(); + controller.signIn(function() { + ok(assertion, "we have an assertion"); + start(); + }); } }); diff --git a/resources/static/test/qunit/mocks/xhr.js b/resources/static/test/qunit/mocks/xhr.js index f4ad2c25de8f826d362f31ae8b1be924215ca5aa..f77340dc6e2867596341b02682b400a84cb9543b 100644 --- a/resources/static/test/qunit/mocks/xhr.js +++ b/resources/static/test/qunit/mocks/xhr.js @@ -129,6 +129,7 @@ BrowserID.Mocks.xhr = (function() { "get /wsapi/address_info?email=unregistered%40testuser.com throttle": { type: "secondary", known: false }, "get /wsapi/address_info?email=unregistered%40testuser.com unknown_secondary": { type: "secondary", known: false }, "get /wsapi/address_info?email=registered%40testuser.com known_secondary": { type: "secondary", known: true }, + "get /wsapi/address_info?email=registered%40testuser.com primary": { type: "primary", auth: "https://auth_url", prov: "https://prov_url" }, "get /wsapi/address_info?email=unregistered%40testuser.com primary": { type: "primary", auth: "https://auth_url", prov: "https://prov_url" }, "get /wsapi/address_info?email=testuser%40testuser.com unknown_secondary": { type: "secondary", known: false }, "get /wsapi/address_info?email=testuser%40testuser.com known_secondary": { type: "secondary", known: true }, diff --git a/resources/static/test/qunit/shared/user_unit_test.js b/resources/static/test/qunit/shared/user_unit_test.js index 7e081cee2566a54ea37d0a94e7413918018fad95..dd9863e46a61ccc047f6fb547591721572e73f94 100644 --- a/resources/static/test/qunit/shared/user_unit_test.js +++ b/resources/static/test/qunit/shared/user_unit_test.js @@ -1094,19 +1094,12 @@ var vep = require("./vep"); lib.syncEmails(function() { xhr.useResult("ajaxError"); - lib.logoutUser(function() { - ok(false, "xhr failure should never succeed"); - start(); - }, function() { - ok(true, "xhr failure should always be a failure"); - start(); - }); - - + lib.logoutUser( + testHelpers.unexpectedSuccess, + testHelpers.expectedXHRFailure + ); }, testHelpers.unexpectedXHRFailure); }, testHelpers.unexpectedXHRFailure); - - }); asyncTest("cancelUser", function(onSuccess) { @@ -1114,22 +1107,15 @@ var vep = require("./vep"); var storedIdentities = storage.getEmails(); equal(_.size(storedIdentities), 0, "All items have been removed"); start(); - }); - - + }, testHelpers.unexpectedXHRFailure); }); asyncTest("cancelUser with XHR failure", function(onSuccess) { xhr.useResult("ajaxError"); - lib.cancelUser(function() { - ok(false, "xhr failure should never succeed"); - start(); - }, function() { - ok(true, "xhr failure should always be a failure"); - start(); - }); - - + lib.cancelUser( + testHelpers.unexpectedSuccess, + testHelpers.expectedXHRFailure + ); }); asyncTest("getPersistentSigninAssertion with invalid login - expect null assertion", function() { @@ -1143,13 +1129,8 @@ var vep = require("./vep"); lib.getPersistentSigninAssertion(function onComplete(assertion) { strictEqual(assertion, null, "assertion with invalid login is null"); start(); - }, function onFailure() { - ok(false, "no expected XHR failure"); - start(); - }); - }); - - + }, testHelpers.unexpectedXHRFailure); + }, testHelpers.unexpectedXHRFailure); }); asyncTest("getPersistentSigninAssertion without email set for site - expect null assertion", function() { @@ -1160,12 +1141,7 @@ var vep = require("./vep"); lib.getPersistentSigninAssertion(function onComplete(assertion) { strictEqual(assertion, null, "assertion with no email is null"); start(); - }, function onFailure() { - ok(false, "no expected XHR failure"); - start(); - }); - - + }, testHelpers.unexpectedXHRFailure); }); asyncTest("getPersistentSigninAssertion without remember set for site - expect null assertion", function() { @@ -1180,10 +1156,7 @@ var vep = require("./vep"); lib.getPersistentSigninAssertion(function onComplete(assertion) { strictEqual(assertion, null, "assertion with remember=false is null"); start(); - }, function onFailure() { - ok(false, "no expected XHR failure"); - start(); - }); + }, testHelpers.unexpectedXHRFailure); }); }); @@ -1199,10 +1172,7 @@ var vep = require("./vep"); lib.getPersistentSigninAssertion(function onComplete(assertion) { ok(assertion, "we have an assertion!"); start(); - }, function onFailure() { - ok(false, "no expected XHR failure"); - start(); - }); + }, testHelpers.unexpectedXHRFailure); }); }); @@ -1217,13 +1187,10 @@ var vep = require("./vep"); xhr.useResult("ajaxError"); - lib.getPersistentSigninAssertion(function onComplete(assertion) { - ok(false, "ajax error should not pass"); - start(); - }, function onFailure() { - ok(true, "ajax error should not pass"); - start(); - }); + lib.getPersistentSigninAssertion( + testHelpers.unexpectedSuccess, + testHelpers.expectedXHRFailure + ); }); @@ -1235,12 +1202,7 @@ var vep = require("./vep"); lib.clearPersistentSignin(function onComplete(success) { strictEqual(success, false, "success with invalid login is false"); start(); - }, function onFailure() { - ok(false, "no expected XHR failure"); - start(); - }); - - + }, testHelpers.unexpectedXHRFailure); }); asyncTest("clearPersistentSignin with valid login with remember set to true", function() { @@ -1251,11 +1213,27 @@ var vep = require("./vep"); strictEqual(success, true, "success flag good"); strictEqual(storage.site.get(testOrigin, "remember"), false, "remember flag set to false"); start(); - }, function onFailure() { - ok(false, "no expected XHR failure"); - start(); - }); + }, testHelpers.unexpectedXHRFailure); + }); + + asyncTest("addressInfo with XHR Error", function() { + ok(false, "write some tests"); + start(); + }); + + asyncTest("addressInfo with secondary user", function() { + ok(false, "write some tests"); + start(); + }); + asyncTest("addressInfo with primary authenticated user", function() { + ok(false, "write some tests"); + start(); + }); + asyncTest("addressInfo with primary unauthenticated user", function() { + ok(false, "write some tests"); + start(); }); + }()); diff --git a/resources/static/test/qunit/testHelpers/helpers.js b/resources/static/test/qunit/testHelpers/helpers.js index e8b84ae618d9e8fc0c441ec5456e9479fc3faa0b..f33e780047e48f61154a7af88195dd2cc1b3a42e 100644 --- a/resources/static/test/qunit/testHelpers/helpers.js +++ b/resources/static/test/qunit/testHelpers/helpers.js @@ -57,6 +57,7 @@ screens.error.hide(); tooltip.reset(); provisioning.setStatus(provisioning.NOT_AUTHENTICATED); + user.reset(); user.init({ provisioning: provisioning });