diff --git a/resources/static/dialog/controllers/required_email.js b/resources/static/dialog/controllers/required_email.js index cd83eda405a2eb22c33587438f5665d85328215a..77b05df67fd932b51e553fc261e2731f7f345723 100644 --- a/resources/static/dialog/controllers/required_email.js +++ b/resources/static/dialog/controllers/required_email.js @@ -129,29 +129,30 @@ BrowserID.Modules.RequiredEmail = (function() { // (without a password). Otherwise, make the user verify the address // (which shows no password). var userOwnsEmail = !!user.getStoredEmailKeypair(email); - showTemplate({ - signin: userOwnsEmail, - showPassword: false - }); + showTemplate({ signin: userOwnsEmail, showPassword: false }); ready(); } else { user.addressInfo(email, function(info) { if(info.type === "primary") { - // For a primary, they should authenticate with the IdP, the normal - // process will take care of the rest. - self.close("primary_user", _.extend(info, { email: email })); - ready(); + user.isUserAuthenticatedToPrimary(email, info, function(authed) { + if(authed) { + showTemplate({ signin: true, showPassword: false }); + } + else { + // For unauthed primary, they should authenticate with the IdP, + // the normal process will take care of the rest. + self.close("primary_user", _.extend({ email: email }, info)); + } + ready(); + }, self.getErrorDialog(errors.addressInfo, ready)); } else { var registered = info.known; // If the current email address is registered but the user is not // authenticated, make them sign in with it. Otherwise, make them // verify ownership of the address. - showTemplate({ - signin: registered, - showPassword: registered - }); + showTemplate({ signin: registered, showPassword: registered }); ready(); } }, self.getErrorDialog(errors.addressInfo, ready)); diff --git a/resources/static/shared/user.js b/resources/static/shared/user.js index 0380ba78a4299cac74924f39d958c56a1b24ea35..1771c153636c7bb8b0cee5c4f435bd7e143c3073 100644 --- a/resources/static/shared/user.js +++ b/resources/static/shared/user.js @@ -369,33 +369,99 @@ BrowserID.User = (function() { } }, + /** + * A full provision a primary user, if they are authenticated, save their + * cert/keypair, and authenticate them to BrowserID. + * @method provisionPrimaryUser + * @param {string} email + * @param {object} info - provisioning info + * @param {function} [onComplete] - called when complete. Called with + * status field and info. Status can be: + * primary.already_added + * primary.verified + * primary.verify + * primary.could_not_add + * @param {function} [onFailure] - called on failure + */ provisionPrimaryUser: function(email, info, onComplete, onFailure) { - provisioning({ - email: email, - url: info.prov - }, function(keypair, cert) { - persistEmailKeypair(email, "primary", keypair, cert, function() { - // We are getting an assertion for browserid.org. - User.getAssertion(email, "https://browserid.org", function(assertion) { - if (assertion) { - onComplete("primary.verified", { - assertion: assertion - }); - } - else { - // XXX change this to could_not_provision - onComplete("primary.could_not_add"); + User.primaryUserAuthenticationInfo(email, info, function(authInfo) { + if(authInfo.authenticated) { + persistEmailKeypair(email, "primary", authInfo.keypair, authInfo.cert, + function() { + // We are getting an assertion for browserid.org. + User.getAssertion(email, "https://browserid.org", function(assertion) { + if (assertion) { + onComplete("primary.verified", { + assertion: assertion + }); + } + else { + // XXX change this to could_not_provision + onComplete("primary.could_not_add"); + } + }, onFailure); } - }, onFailure); - }, onFailure); - }, function(error) { - if (error.code === "primaryError" && error.msg === "user is not authenticated as target user") { - onComplete("primary.verify", info); + ); } else { - onFailure(info); + onComplete("primary.verify", info); } - }); + }, onFailure); + }, + + /** + * Get the IdP authentication info for a user. + * @method primaryUserAuthenticationInfo + * @param {string} email + * @param {object} info - provisioning info + * @param {function} [onComplete] - called when complete. Called with + * provisioning info as well as keypair, cert, and authenticated. + * authenticated - boolean, true if user is authenticated with primary. + * false otw. + * keypair - returned if user is authenticated. + * cert - returned if user is authenticated. + * @param {function} [onFailure] - called on failure + */ + primaryUserAuthenticationInfo: function(email, info, onComplete, onFailure) { + provisioning( + { email: email, url: info.prov }, + function(keypair, cert) { + var userInfo = _.extend({ + keypair: keypair, + cert: cert, + authenticated: true + }, info); + + onComplete(userInfo); + }, + function(error) { + if (error.code === "primaryError" && error.msg === "user is not authenticated as target user") { + var userInfo = _.extend({ + authenticated: false + }, info); + onComplete(userInfo); + } + else { + onFailure(info); + } + } + ); + + }, + + /** + * Get the IdP authentication status for a user. + * @method isUserAuthenticatedToPrimary + * @param {string} email + * @param {object} info - provisioning info + * @param {function} [onComplete] - called when complete. Called with + * status field - true if user authenticated with IdP, false otw. + * @param {function} [onFailure] - called on failure + */ + isUserAuthenticatedToPrimary: function(email, info, onComplete, onFailure) { + User.primaryUserAuthenticationInfo(email, info, function(authInfo) { + onComplete(authInfo.authenticated); + }, onFailure); }, /** 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 9ad563aa7bf326941630bc8160edff731ed25d5b..d9b582ce7e2b3052b0c9a12e1a94a8dc40f8d582 100644 --- a/resources/static/test/qunit/controllers/required_email_unit_test.js +++ b/resources/static/test/qunit/controllers/required_email_unit_test.js @@ -43,7 +43,8 @@ user = bid.User, testHelpers = bid.TestHelpers, register = testHelpers.register, - mediator = bid.Mediator; + provisioning = bid.Mocks.Provisioning; + module("controllers/required_email", { setup: function() { @@ -123,22 +124,38 @@ }); }); - asyncTest("user who is not authenticated, primary - user must validate with IdP.", function() { + asyncTest("user who is not authenticated, authenticated with IdP - user sees sign in screen.", function() { + var email = "unregistered@testuser.com"; + + xhr.useResult("primary"); + provisioning.setStatus(provisioning.AUTHENTICATED); + + createController({ + email: email, + authenticated: false, + ready: function() { + testSignIn(email, testNoPasswordSection); + } + }); + }); + + asyncTest("user who is not authenticated, not authenticated with IdP - user sees verification screen.", function() { var email = "unregistered@testuser.com", msgInfo; - mediator.subscribe("primary_user", function(msg, info) { + register("primary_user", function(msg, info) { msgInfo = info; }); xhr.useResult("primary"); + provisioning.setStatus(provisioning.NOT_AUTHENTICATED); + createController({ email: email, authenticated: false, ready: function() { - equal(msgInfo.email, "unregistered@testuser.com", "correct email address"); + equal(msgInfo && msgInfo.email, "unregistered@testuser.com", "correct email address"); start(); - } }); }); diff --git a/resources/static/test/qunit/shared/user_unit_test.js b/resources/static/test/qunit/shared/user_unit_test.js index c0591f97a4aa4118baa8068745b2a294e5af6220..b191f8590c080a47120d221593b1980500211220 100644 --- a/resources/static/test/qunit/shared/user_unit_test.js +++ b/resources/static/test/qunit/shared/user_unit_test.js @@ -243,6 +243,110 @@ var vep = require("./vep"); start(); }); + asyncTest("provisionPrimaryUser authenticated with IdP, expect primary.verified", function() { + xhr.useResult("primary"); + provisioning.setStatus(provisioning.AUTHENTICATED); + + lib.provisionPrimaryUser("unregistered@testuser.com", {}, + function(status, info) { + equal(status, "primary.verified", "primary user is already verified, correct status"); + start(); + }, + testHelpers.unexpectedXHRFailure + ); + }); + + asyncTest("provisionPrimaryUser not authenticated with IdP, expect primary.verify", function() { + xhr.useResult("primary"); + provisioning.setStatus(provisioning.NOT_AUTHENTICATED); + + lib.provisionPrimaryUser("unregistered@testuser.com", {}, + function(status, info) { + equal(status, "primary.verify", "primary user is not verified, correct status"); + start(); + }, + testHelpers.unexpectedXHRFailure + ); + }); + + asyncTest("provisionPrimaryUser with provisioning failure - call failure", function() { + xhr.useResult("primary"); + provisioning.setFailure("failure"); + + lib.provisionPrimaryUser("unregistered@testuser.com", {}, + testHelpers.unexpectedSuccess, + testHelpers.expectedXHRFailure + ); + }); + + asyncTest("primaryUserAuthenticationInfo, user authenticated to IdP, expect keypair, cert, authenticated status", function() { + provisioning.setStatus(provisioning.AUTHENTICATED); + + lib.primaryUserAuthenticationInfo("testuser@testuser.com", {}, + function(info) { + equal(info.authenticated, true, "user is authenticated"); + ok(info.keypair, "keypair passed"); + ok(info.cert, "cert passed"); + start(); + }, + testHelpers.unexpectedXHRError + ); + }); + + asyncTest("primaryUserAuthenticationInfo, user not authenticated to IdP, expect false authenticated status", function() { + provisioning.setStatus(provisioning.NOT_AUTHENTICATED); + + lib.primaryUserAuthenticationInfo("testuser@testuser.com", {}, + function(info) { + equal(info.authenticated, false, "user is not authenticated"); + start(); + }, + testHelpers.unexpectedXHRError + ); + }); + + asyncTest("primaryUserAuthenticationInfo with XHR failure", function() { + provisioning.setFailure("failure"); + + lib.primaryUserAuthenticationInfo("testuser@testuser.com", {}, + testHelpers.unexpectedSuccess, + testHelpers.expectedXHRFailure + ); + }); + + asyncTest("isUserAuthenticatedToPrimary with authed user, expect true status", function() { + provisioning.setStatus(provisioning.AUTHENTICATED); + + lib.isUserAuthenticatedToPrimary("testuser@testuser.com", {}, + function(status) { + equal(status, true, "user is authenticated, correct status"); + start(); + }, + testHelpers.unexpectedXHRError + ); + }); + + asyncTest("isUserAuthenticatedToPrimary with non-authed user, expect false status", function() { + provisioning.setStatus(provisioning.NOT_AUTHENTICATED); + + lib.isUserAuthenticatedToPrimary("testuser@testuser.com", {}, + function(status) { + equal(status, false, "user is not authenticated, correct status"); + start(); + }, + testHelpers.unexpectedXHRError + ); + }); + + asyncTest("isUserAuthenticatedToPrimary with failure", function() { + provisioning.setFailure("failure"); + + lib.isUserAuthenticatedToPrimary("testuser@testuser.com", {}, + testHelpers.unexpectedSuccess, + testHelpers.expectedXHRFailure + ); + }); + asyncTest("waitForUserValidation with `complete` response", function() { storage.setStagedOnBehalfOf(testOrigin); @@ -534,7 +638,6 @@ var vep = require("./vep"); }); }); - asyncTest("isEmailRegistered with registered email", function() { lib.isEmailRegistered("registered@testuser.com", function(registered) { ok(registered);