From 2b8a3bdb63b8ece7f98816e1b28c24b5c64974cd Mon Sep 17 00:00:00 2001 From: Shane Tomlinson <stomlinson@mozilla.com> Date: Fri, 23 Dec 2011 12:17:21 +0000 Subject: [PATCH] Woot! We can now redirect to an IdP page to authenticate and then back. * Hacktastic and minimal tests - loads of tests needed. * Assume if the #EMAIL=<email> hash is specified on the URL, then try provisioning the user. * Fixed all previously broken, non-completing tests. * Added several new controllers - email_chosen, provision_primary_user, verify_primary_user. --- .../static/dialog/controllers/actions.js | 24 ++-- .../static/dialog/controllers/authenticate.js | 40 +------ resources/static/dialog/controllers/dialog.js | 19 +-- .../static/dialog/controllers/email_chosen.js | 64 ++++++++++ resources/static/dialog/controllers/page.js | 4 +- .../static/dialog/controllers/pickemail.js | 13 ++- .../controllers/provision_primary_user.js | 109 ++++++++++++++++++ .../dialog/controllers/verify_primary_user.js | 5 +- resources/static/dialog/resources/helpers.js | 49 ++------ .../static/dialog/resources/state_machine.js | 8 ++ resources/static/dialog/start.js | 2 + resources/static/shared/user.js | 93 +++++++-------- resources/static/test/index.html | 2 + .../qunit/controllers/actions_unit_test.js | 28 ++++- .../qunit/controllers/dialog_unit_test.js | 70 ++--------- .../controllers/email_chosen_unit_test.js | 78 +++++++++++++ .../qunit/controllers/pickemail_unit_test.js | 6 +- .../verify_primary_user_unit_test.js | 8 +- resources/static/test/qunit/mocks/window.js | 5 +- .../test/qunit/resources/helpers_unit_test.js | 32 ----- resources/views/dialog_layout.ejs | 2 + scripts/compress.sh | 2 +- 22 files changed, 411 insertions(+), 252 deletions(-) create mode 100644 resources/static/dialog/controllers/email_chosen.js create mode 100644 resources/static/dialog/controllers/provision_primary_user.js create mode 100644 resources/static/test/qunit/controllers/email_chosen_unit_test.js diff --git a/resources/static/dialog/controllers/actions.js b/resources/static/dialog/controllers/actions.js index 2640fe368..ccead718c 100644 --- a/resources/static/dialog/controllers/actions.js +++ b/resources/static/dialog/controllers/actions.js @@ -51,8 +51,12 @@ BrowserID.Modules.Actions = (function() { if(runningService) { serviceManager.stop(runningService); } - runningService = name; - return serviceManager.start(name, options); + var module = serviceManager.start(name, options); + if(module) { + runningService = name; + } + + return module; } function startRegCheckService(email, verifier, message) { @@ -77,9 +81,7 @@ BrowserID.Modules.Actions = (function() { sc.start.call(self, data); - if(data.ready) { - data.ready(); - } + if(data.ready) _.defer(data.ready); }, /** @@ -101,7 +103,7 @@ BrowserID.Modules.Actions = (function() { }, doCancel: function() { - onsuccess && onsuccess(null); + if(onsuccess) onsuccess(null); }, doConfirmUser: function(email) { @@ -147,7 +149,7 @@ BrowserID.Modules.Actions = (function() { // calls window.close, which would trigger the onerror callback if we // tried this afterwards. onerror = null; - onsuccess && onsuccess(assertion); + if(onsuccess) onsuccess(assertion); }, doNotMe: function() { @@ -170,8 +172,16 @@ BrowserID.Modules.Actions = (function() { }, self.getErrorDialog(errors.checkAuthentication)); }, + doProvisionPrimaryUser: function(info) { + startService("provision_primary_user", info); + }, + doVerifyPrimaryUser: function(info) { startService("verify_primary_user", info); + }, + + doEmailChosen: function(info) { + startService("email_chosen", info); } }); diff --git a/resources/static/dialog/controllers/authenticate.js b/resources/static/dialog/controllers/authenticate.js index 40a437cb6..e359896d2 100644 --- a/resources/static/dialog/controllers/authenticate.js +++ b/resources/static/dialog/controllers/authenticate.js @@ -54,39 +54,6 @@ BrowserID.Modules.Authenticate = (function() { return helpers.getAndValidateEmail("#email"); } - function provisionPrimaryUser(email, info, oncomplete) { - var self=this; - - function complete(status) { - oncomplete && oncomplete(status); - } - - user.provisionPrimaryUser(email, info, function(status, status_info) { - switch(status) { - case "primary.already_added": - // XXX Is this status possible? - break; - case "primary.verified": - self.close("primary_user_verified", status_info); - complete(true); - break; - case "primary.verify": - self.close("primary_verify_user", { - email: email, - auth_url: info.auth - }); - complete(true); - break; - case "primary.could_not_add": - // XXX Can this happen? - break; - default: - break; - } - }, self.getErrorDialog(errors.provisioningPrimary)); - - } - function checkEmail() { var email = getEmail(), self = this; @@ -95,7 +62,10 @@ BrowserID.Modules.Authenticate = (function() { user.addressInfo(email, function(info) { if(info.type === "primary") { - provisionPrimaryUser.call(self, email, info); + self.close("provision_primary_user", { + email: email, + info: info + }); } else { if(info.known) { @@ -113,7 +83,7 @@ BrowserID.Modules.Authenticate = (function() { email = getEmail(); if (email) { - dialogHelpers.createUser.call(self, email, info, callback); + dialogHelpers.createUser.call(self, email, callback); } else { callback && callback(); } diff --git a/resources/static/dialog/controllers/dialog.js b/resources/static/dialog/controllers/dialog.js index f52c5169e..af3acc7bc 100644 --- a/resources/static/dialog/controllers/dialog.js +++ b/resources/static/dialog/controllers/dialog.js @@ -74,7 +74,8 @@ BrowserID.Modules.Dialog = (function() { } function startChannel() { - var self = this; + var self = this, + hash = win.location.hash; // first, we see if there is a local channel if (win.navigator.id && win.navigator.id.channel) { @@ -83,7 +84,7 @@ BrowserID.Modules.Dialog = (function() { } // next, we see if the caller intends to call native APIs - if (win.location.hash == "#NATIVE" || win.location.hash == "#INTERNAL") { + if (hash == "#NATIVE" || hash == "#INTERNAL") { // don't do winchan, let it be. return; } @@ -129,9 +130,7 @@ BrowserID.Modules.Dialog = (function() { win = options.window || window; sc.start.call(self, options); - startChannel.call(self); - options.ready && _.defer(options.ready); }, @@ -145,7 +144,8 @@ BrowserID.Modules.Dialog = (function() { }, get: function(origin_url, params, success, error) { - var self=this; + var self=this, + hash = win.location.hash; setOrigin(origin_url); @@ -159,8 +159,13 @@ BrowserID.Modules.Dialog = (function() { // XXX Perhaps put this into the state machine. self.bind(win, "unload", onWindowUnload); - - self.publish("start", params); + if(hash.indexOf("#EMAIL=") === 0) { + var email = hash.replace(/#EMAIL=/, ""); + self.close("provision_primary_user", { email: email }); + } + else { + self.publish("start", params); + } } } diff --git a/resources/static/dialog/controllers/email_chosen.js b/resources/static/dialog/controllers/email_chosen.js new file mode 100644 index 000000000..1042d1eef --- /dev/null +++ b/resources/static/dialog/controllers/email_chosen.js @@ -0,0 +1,64 @@ +/*jshint brgwser:true, jQuery: true, forin: true, laxbreak:true */ +/*global _: true, BrowserID: true, PageController: true */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +BrowserID.Modules.EmailChosen = (function() { + "use strict"; + + var bid = BrowserID, + dialogHelpers = bid.Helpers.Dialog, + sc; + + var EmailChosen = bid.Modules.PageModule.extend({ + start: function(options) { + var email = options.email, + self=this; + + if(!email) { + throw "email required"; + } + + dialogHelpers.getAssertion.call(self, email, options.ready); + + sc.start.call(self, options); + } + }); + + sc = EmailChosen.sc; + + return EmailChosen; + +}()); + diff --git a/resources/static/dialog/controllers/page.js b/resources/static/dialog/controllers/page.js index 149c1a349..e0dceb821 100644 --- a/resources/static/dialog/controllers/page.js +++ b/resources/static/dialog/controllers/page.js @@ -75,7 +75,7 @@ BrowserID.Modules.PageModule = (function() { } }, - start: function() { + start: function(options) { var self=this; self.bind("form", "submit", onSubmit); self.bind("#thisIsNotMe", "click", self.close.bind(self, "notme")); @@ -164,7 +164,7 @@ BrowserID.Modules.PageModule = (function() { self.renderError("error", $.extend({ action: action }, lowLevelInfo), onerror); - } + }; } }); diff --git a/resources/static/dialog/controllers/pickemail.js b/resources/static/dialog/controllers/pickemail.js index 3358e3dbc..9aa9547b5 100644 --- a/resources/static/dialog/controllers/pickemail.js +++ b/resources/static/dialog/controllers/pickemail.js @@ -37,15 +37,13 @@ BrowserID.Modules.PickEmail = (function() { "use strict"; - var ANIMATION_TIME = 250, - bid = BrowserID, + var bid = BrowserID, user = bid.User, errors = bid.Errors, storage = bid.Storage, helpers = bid.Helpers, dialogHelpers = helpers.Dialog, - dom = bid.DOM, - assertion; + dom = bid.DOM; function cancelEvent(event) { event && event.preventDefault(); @@ -96,7 +94,7 @@ BrowserID.Modules.PickEmail = (function() { storage.site.set(origin, "remember", $("#remember").is(":checked")); } - dialogHelpers.getAssertion.call(self, email); + self.close("email_chosen", { email: email }); } } @@ -137,10 +135,13 @@ BrowserID.Modules.PickEmail = (function() { stop: function() { PickEmail.sc.stop.call(this); dom.removeClass("body", "pickemail"); - }, + } + // BEGIN TESTING API + , signIn: signIn, addEmail: addEmail + // END TESTING API }); return PickEmail; diff --git a/resources/static/dialog/controllers/provision_primary_user.js b/resources/static/dialog/controllers/provision_primary_user.js new file mode 100644 index 000000000..00956f40b --- /dev/null +++ b/resources/static/dialog/controllers/provision_primary_user.js @@ -0,0 +1,109 @@ +/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */ +/*global BrowserID:true, PageController: true */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +BrowserID.Modules.ProvisionPrimaryUser = (function() { + "use strict"; + + var ANIMATION_TIME = 250, + bid = BrowserID, + user = bid.User, + errors = bid.Errors; + + function provisionPrimaryUser(email, info, oncomplete) { + var self=this; + + function complete(status) { + oncomplete && oncomplete(status); + } + + localStorage.IdPInfo = JSON.stringify(info); + + user.provisionPrimaryUser(email, info, function(status, status_info) { + switch(status) { + case "primary.already_added": + // XXX Is this status possible? + break; + case "primary.verified": + self.close("email_chosen", { email: email } ); + complete(true); + break; + case "primary.verify": + self.close("primary_verify_user", { + email: email, + auth_url: info.auth + }); + complete(true); + break; + case "primary.could_not_add": + // XXX Can this happen? + break; + default: + break; + } + }, self.getErrorDialog(errors.provisioningPrimary)); + } + + var ProvisionPrimaryUser = bid.Modules.PageModule.extend({ + start: function(options) { + options = options || {}; + + var self = this, + email = options.email, + info = options.info; + + if(!(info && info.auth)) { + info = JSON.parse(localStorage.IdPInfo); + localStorage.removeItem("IdPInfo"); + } + + if(!(info.auth && info.prov)) { + throw "cannot provision a primary user without an auth and prov URLs"; + } + + provisionPrimaryUser.call(self, email, info); + + ProvisionPrimaryUser.sc.start.call(self, options); + } + + // BEGIN TESTING API + , + provisionPrimaryUser: provisionPrimaryUser + // END TESTING API + }); + + return ProvisionPrimaryUser; + +}()); diff --git a/resources/static/dialog/controllers/verify_primary_user.js b/resources/static/dialog/controllers/verify_primary_user.js index 0966e5a06..d69805591 100644 --- a/resources/static/dialog/controllers/verify_primary_user.js +++ b/resources/static/dialog/controllers/verify_primary_user.js @@ -47,9 +47,12 @@ BrowserID.Modules.VerifyPrimaryUser = (function() { function verify(callback) { this.publish("primary_verifying_user"); + // replace any hashes that may be there already. + var returnTo = win.document.location.href.replace(/#.*$/, ""); + var url = helpers.toURL(auth_url, { email: email, - return_to: win.document.location + return_to: returnTo + "#EMAIL="+email }); win.document.location = url; diff --git a/resources/static/dialog/resources/helpers.js b/resources/static/dialog/resources/helpers.js index 4428d2e85..b224c0859 100644 --- a/resources/static/dialog/resources/helpers.js +++ b/resources/static/dialog/resources/helpers.js @@ -90,49 +90,22 @@ }, self.getErrorDialog(errors.authenticate)); } - function createUser(email, info, callback) { + function createUser(email, callback) { function complete(status) { callback && callback(status); } var self=this; - user.createUserWithInfo(email, info, function(status, info) { - switch(status) { - case "secondary.already_added": - // XXX how to handle this - createUser should not be called on - // already existing addresses, so this path should not be called. - tooltip.showTooltip("#already_registered"); - complete(false); - break; - case "secondary.verify": - self.close("user_staged", { - email: email - }); - complete(true); - break; - case "secondary.could_not_add": - tooltip.showTooltip("#could_not_add"); - complete(false); - break; - case "primary.already_added": - // XXX Is this status possible? - break; - case "primary.verified": - self.close("primary_user_verified", info); - complete(true); - break; - case "primary.verify": - self.close("primary_verify_user", { - email: email, - auth_url: info.auth - }); - complete(true); - break; - case "primary.could_not_add": - // XXX Can this happen? - break; - default: - break; + user.createSecondaryUser(email, function(status) { + if(status) { + self.close("user_staged", { + email: email + }); + complete(true); + } + else { + tooltip.showTooltip("#could_not_add"); + complete(false); } }, self.getErrorDialog(errors.createUser, callback)); } diff --git a/resources/static/dialog/resources/state_machine.js b/resources/static/dialog/resources/state_machine.js index 84a3ec844..b7ef79511 100644 --- a/resources/static/dialog/resources/state_machine.js +++ b/resources/static/dialog/resources/state_machine.js @@ -147,6 +147,10 @@ gotoState("doEmailConfirmed"); }); + subscribe("provision_primary_user", function(msg, info) { + gotoState("doProvisionPrimaryUser", info); + }); + subscribe("primary_user_verified", function(msg, info) { mediator.publish("assertion_generated", info); }); @@ -173,6 +177,10 @@ }); }); + subscribe("email_chosen", function(msg, info) { + gotoState("doEmailChosen", info); + }); + subscribe("notme", function() { gotoState("doNotMe"); }); diff --git a/resources/static/dialog/start.js b/resources/static/dialog/start.js index 944649ef0..bcc5bfece 100644 --- a/resources/static/dialog/start.js +++ b/resources/static/dialog/start.js @@ -19,6 +19,8 @@ moduleManager.register("pick_email", modules.PickEmail); moduleManager.register("required_email", modules.RequiredEmail); moduleManager.register("verify_primary_user", modules.VerifyPrimaryUser); + moduleManager.register("provision_primary_user", modules.ProvisionPrimaryUser); + moduleManager.register("email_chosen", modules.EmailChosen); moduleManager.start("dialog"); } diff --git a/resources/static/shared/user.js b/resources/static/shared/user.js index 1effdd204..460f50631 100644 --- a/resources/static/shared/user.js +++ b/resources/static/shared/user.js @@ -1,5 +1,5 @@ /*jshint browsers:true, forin: true, laxbreak: true */ -/*global _: true, BrowserID: true */ +/*global _: true, BrowserID: true, console: true */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -152,7 +152,7 @@ BrowserID.User = (function() { onFailure(status); } }, onFailure); - }; + } poll(); } @@ -164,52 +164,20 @@ BrowserID.User = (function() { } } - /** - * Certify an identity with the server, persist it to storage if the server - * says the identity is good - * @method certifyEmailKeypair - */ - function certifyEmailKeypair(email, keypair, onSuccess, onFailure) { - network.certKey(email, keypair.publicKey, function(cert) { - // emails that *we* certify are always secondary emails - persistEmailKeypair(email, "secondary", keypair, cert, onSuccess, onFailure); - }, onFailure); - } - function checkEmailType(type) { if (type !== 'secondary' && type !== 'primary') throw "invalid email type (should be 'secondary' or 'primary'): " + type; } - /** - * Persist an email address without a keypair - * @method persistEmail - * @param {string} email - Email address to persist. - * @param {string} type - Is the email a 'primary' or a 'secondary' address? - * @param {function} [onSuccess] - Called on successful completion. - * @param {function} [onFailure] - Called on error. - */ - function persistEmail(email, type, onSuccess, onFailure) { - checkEmailType(type); - storage.addEmail(email, { - created: new Date(), - type: type - }); - - if (onSuccess) { - onSuccess(); - } - } - /** * Persist an address and key pair locally. * @method persistEmailKeypair * @param {string} email - Email address to persist. * @param {object} keypair - Key pair to save - * @param {function} [onSuccess] - Called on successful completion. + * @param {function} [onComplete] - Called on successful completion. * @param {function} [onFailure] - Called on error. */ - function persistEmailKeypair(email, type, keypair, cert, onSuccess, onFailure) { + function persistEmailKeypair(email, type, keypair, cert, onComplete, onFailure) { checkEmailType(type); var now = new Date(); var email_obj = storage.getEmails()[email] || { @@ -225,10 +193,37 @@ BrowserID.User = (function() { }); storage.addEmail(email, email_obj); + if(onComplete) onComplete(true); + } - if (onSuccess) { - onSuccess(); - } + /** + * Certify an identity with the server, persist it to storage if the server + * says the identity is good + * @method certifyEmailKeypair + */ + function certifyEmailKeypair(email, keypair, onComplete, onFailure) { + network.certKey(email, keypair.publicKey, function(cert) { + // emails that *we* certify are always secondary emails + persistEmailKeypair(email, "secondary", keypair, cert, onComplete, onFailure); + }, onFailure); + } + + /** + * Persist an email address without a keypair + * @method persistEmail + * @param {string} email - Email address to persist. + * @param {string} type - Is the email a 'primary' or a 'secondary' address? + * @param {function} [onComplete] - Called on successful completion. + * @param {function} [onFailure] - Called on error. + */ + function persistEmail(email, type, onComplete, onFailure) { + checkEmailType(type); + storage.addEmail(email, { + created: new Date(), + type: type + }); + + if(onComplete) onComplete(true); } User = { @@ -360,17 +355,8 @@ BrowserID.User = (function() { User.getAssertion(email, "https://browserid.org", function(assertion) { if(assertion) { network.authenticateWithAssertion(email, assertion, function(status) { - if(status) { - User.getAssertion(email, User.getOrigin(), function(assertion) { - onComplete("primary.verified", { - email: email, - assertion: assertion - }); - }, onFailure); - } - else { - onComplete("primary.could_not_add"); - } + var message = status ? "primary.verified" : "primary.could_not_add"; + onComplete(message); }, onFailure); } else { @@ -761,10 +747,11 @@ BrowserID.User = (function() { * @method syncEmailKeypair * @param {string} email - Email address. * @param {string} [issuer] - Issuer of keypair. - * @param {function} [onSuccess] - Called on successful completion. + * @param {function} [onComplete] - Called on completion. Called with + * status parameter - true if successful, false otw. * @param {function} [onFailure] - Called on error. */ - syncEmailKeypair: function(email, onSuccess, onFailure) { + syncEmailKeypair: function(email, onComplete, onFailure) { prepareDeps(); // FIXME: parameterize! var keysize = 256; @@ -773,7 +760,7 @@ BrowserID.User = (function() { keysize = 128; var keypair = jwk.KeyPair.generate("DS", keysize); setTimeout(function() { - certifyEmailKeypair(email, keypair, onSuccess, onFailure); + certifyEmailKeypair(email, keypair, onComplete, onFailure); }, 0); }, diff --git a/resources/static/test/index.html b/resources/static/test/index.html index b725c20fe..e17f4b15f 100644 --- a/resources/static/test/index.html +++ b/resources/static/test/index.html @@ -129,6 +129,7 @@ <script type="text/javascript" src="/dialog/controllers/forgotpassword.js"></script> <script type="text/javascript" src="/dialog/controllers/required_email.js"></script> <script type="text/javascript" src="/dialog/controllers/verify_primary_user.js"></script> + <script type="text/javascript" src="/dialog/controllers/email_chosen.js"></script> <script type="text/javascript" src="/pages/page_helpers.js"></script> <script type="text/javascript" src="/pages/add_email_address.js"></script> @@ -176,6 +177,7 @@ <script type="text/javascript" src="qunit/controllers/forgotpassword_unit_test.js"></script> <script type="text/javascript" src="qunit/controllers/required_email_unit_test.js"></script> <script type="text/javascript" src="qunit/controllers/verify_primary_user_unit_test.js"></script> + <script type="text/javascript" src="qunit/controllers/email_chosen_unit_test.js"></script> <!-- must go last or all other tests will fail. --> <script type="text/javascript" src="qunit/controllers/dialog_unit_test.js"></script> diff --git a/resources/static/test/qunit/controllers/actions_unit_test.js b/resources/static/test/qunit/controllers/actions_unit_test.js index 0bea359fa..1c6b0597d 100644 --- a/resources/static/test/qunit/controllers/actions_unit_test.js +++ b/resources/static/test/qunit/controllers/actions_unit_test.js @@ -95,17 +95,37 @@ }); }); - /* asyncTest("doVerifyPrimaryUser does something", function() { createController({ ready: function() { - controller.doVerifyPrimaryUser(); - // XXX test something + var error; + try { + controller.doVerifyPrimaryUser(); + } catch(e) { + error = e; + } + + equal(error, "module not registered for verify_primary_user", "correct service started"); + start(); + } + }); + }); + + asyncTest("doEmailChosen does something", function() { + createController({ + ready: function() { + var error; + try { + controller.doEmailChosen({email: "testuser@testuser.com"}); + } catch(e) { + error = e; + } + + equal(error, "module not registered for email_chosen", "correct service started"); start(); } }); }); -*/ }()); diff --git a/resources/static/test/qunit/controllers/dialog_unit_test.js b/resources/static/test/qunit/controllers/dialog_unit_test.js index 8b77d6e35..72e17905f 100644 --- a/resources/static/test/qunit/controllers/dialog_unit_test.js +++ b/resources/static/test/qunit/controllers/dialog_unit_test.js @@ -40,6 +40,7 @@ var bid = BrowserID, channel = bid.Channel, network = bid.Network, + mediator = bid.Mediator, xhr = bid.Mocks.xhr, controller, el, @@ -160,69 +161,19 @@ }); }); - /* - test("doXHRError while online, no network info given", function() { - createController(); - controller.doXHRError(); - ok($("#error .contents").text().length, "contents have been written"); - ok($("#error #action").text().length, "action contents have been written"); - equal($("#error #network").text().length, 0, "no network contents to be written"); - }); - - test("doXHRError while online, network info given", function() { - createController(); - controller.doXHRError({ - network: { - type: "POST", - url: "browserid.org/verify" - } - }); - checkNetworkError(); - }); - - test("doXHRError while offline does not update contents", function() { - createController(); - controller.doOffline(); - $("#error #action").remove(); - - controller.doXHRError(); - ok(!$("#error #action").text().length, "XHR error is not reported if the user is offline."); - }); -*/ - - /* - test("doCheckAuth with registered requiredEmail, authenticated", function() { - createController({ - requiredEmail: "registered@testuser.com" - }); - - controller.doCheckAuth(); - }); + asyncTest("initialization with #EMAIL=testuser@testuser.com", function() { + winMock.location.hash = "#EMAIL=testuser@testuser.com"; - test("doCheckAuth with registered requiredEmail, not authenticated", function() { createController({ - requiredEmail: "registered@testuser.com" - }); - - controller.doCheckAuth(); - }); - - test("doCheckAuth with unregistered requiredEmail, not authenticated", function() { - createController({ - requiredEmail: "unregistered@testuser.com" - }); - - controller.doCheckAuth(); - }); - - test("doCheckAuth with unregistered requiredEmail, authenticated as other user", function() { - createController({ - requiredEmail: "unregistered@testuser.com" + ready: function() { + // XXX do a check here! + mediator.subscribe("email_chosen", function(msg, info) { + equal(info.email, "testuser@testuser.com", "email_chosen with correct email"); + }); + start(); + } }); - - controller.doCheckAuth(); }); -*/ asyncTest("onWindowUnload", function() { createController({ @@ -243,5 +194,6 @@ }); }); + }()); diff --git a/resources/static/test/qunit/controllers/email_chosen_unit_test.js b/resources/static/test/qunit/controllers/email_chosen_unit_test.js new file mode 100644 index 000000000..a0340e8e6 --- /dev/null +++ b/resources/static/test/qunit/controllers/email_chosen_unit_test.js @@ -0,0 +1,78 @@ +/*jshint browsers:true, forin: true, laxbreak: true */ +/*global asyncTest: true, test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID:true */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +(function() { + "use strict"; + + var bid = BrowserID, + controller, + el, + testHelpers = bid.TestHelpers, + mediator = bid.Mediator, + user = bid.User; + + function createController(config, complete) { + config = config || {}; + config.ready = complete; + + controller = BrowserID.Modules.EmailChosen.create(); + controller.start(config); + } + + module("controllers/email_chosen", { + setup: function() { + testHelpers.setup(); + }, + + teardown: function() { + if(controller) { + controller.destroy(); + } + testHelpers.teardown(); + } + }); + + asyncTest("start with email, expect an assertion to be generated", function() { + user.syncEmailKeypair("testuser@testuser.com", function() { + createController( { email: "testuser@testuser.com" }, function(assertion) { + ok(assertion, "assertion generated"); + start(); + }); + }); + }); + +}()); + diff --git a/resources/static/test/qunit/controllers/pickemail_unit_test.js b/resources/static/test/qunit/controllers/pickemail_unit_test.js index d7c4886a2..63f64670d 100644 --- a/resources/static/test/qunit/controllers/pickemail_unit_test.js +++ b/resources/static/test/qunit/controllers/pickemail_unit_test.js @@ -139,10 +139,10 @@ var assertion; - register("assertion_generated", function(msg, info) { + register("email_chosen", function(msg, info) { equal(storage.site.get(testOrigin, "email"), "testuser2@testuser.com", "email saved correctly"); equal(storage.site.get(testOrigin, "remember"), true, "remember saved correctly"); - ok(info.assertion, "assertion_generated message triggered with assertion"); + ok(info.email, "email_chosen message triggered with email"); start(); }); controller.signIn(); @@ -159,7 +159,7 @@ $("input[type=radio]").eq(1).trigger("click"); $("#remember").attr("checked", true); - register("assertion_generated", function(msg, info) { + register("email_chosen", function(msg, info) { equal(storage.site.get(testOrigin, "email"), "testuser2@testuser.com", "email saved correctly"); equal(storage.site.get(testOrigin, "remember"), false, "remember saved correctly"); diff --git a/resources/static/test/qunit/controllers/verify_primary_user_unit_test.js b/resources/static/test/qunit/controllers/verify_primary_user_unit_test.js index e4362850a..b24612acf 100644 --- a/resources/static/test/qunit/controllers/verify_primary_user_unit_test.js +++ b/resources/static/test/qunit/controllers/verify_primary_user_unit_test.js @@ -1,5 +1,5 @@ /*jshint browsers:true, forin: true, laxbreak: true */ -/*global test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID:true */ +/*global asyncTest: true, test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID:true */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * @@ -75,10 +75,12 @@ messageTriggered = true; }); - win.document.location = "sign_in_complete"; + // Also checking to make sure the NATIVE is stripped out. + win.document.location.href = "sign_in"; + win.document.location.hash = "#NATIVE"; controller.submit(function() { - equal(win.document.location, "http://testuser.com/sign_in?email=unregistered%40testuser.com&return_to=sign_in_complete"); + equal(win.document.location, "http://testuser.com/sign_in?email=unregistered%40testuser.com&return_to=sign_in%23EMAIL%3Dunregistered%40testuser.com"); equal(messageTriggered, true, "primary_verifying_user triggered"); start(); }); diff --git a/resources/static/test/qunit/mocks/window.js b/resources/static/test/qunit/mocks/window.js index 2728fb434..db1d49f47 100644 --- a/resources/static/test/qunit/mocks/window.js +++ b/resources/static/test/qunit/mocks/window.js @@ -38,7 +38,10 @@ BrowserID.Mocks.WindowMock = (function() { "use strict"; function DocumentMock() { - this.location = document.location; + this.location = { + href: document.location.href, + hash: document.location.hash + }; } function WindowMock() { diff --git a/resources/static/test/qunit/resources/helpers_unit_test.js b/resources/static/test/qunit/resources/helpers_unit_test.js index 62e6e486a..fe80aee80 100644 --- a/resources/static/test/qunit/resources/helpers_unit_test.js +++ b/resources/static/test/qunit/resources/helpers_unit_test.js @@ -141,16 +141,6 @@ }); }); - asyncTest("createUser with known secondary, user not staged", function() { - closeCB = badClose; - - xhr.useResult("known_secondary"); - dialogHelpers.createUser.call(controllerMock, "registered@testuser.com", function(staged) { - equal(staged, false, "user was not staged"); - start(); - }); - }); - asyncTest("createUser with unknown secondary happy case, expect 'user_staged' message", function() { xhr.useResult("unknown_secondary"); closeCB = expectedClose("user_staged", "email", "unregistered@testuser.com"); @@ -178,28 +168,6 @@ dialogHelpers.createUser.call(controllerMock, "registered@testuser.com", testHelpers.unexpectedSuccess); }); - asyncTest("createUser with unknown primary, user verified - expect 'primary_user_verified' message", function() { - closeCB = expectedClose("primary_user_verified", "email", "unregistered@testuser.com"); - - xhr.useResult("primary"); - provisioning.setStatus(provisioning.AUTHENTICATED); - - dialogHelpers.createUser.call(controllerMock, "unregistered@testuser.com", function(staged) { - equal(staged, true, "user was staged"); - start(); - }); - }); - - asyncTest("createUser with unknown primary, user must verify with IdP - expect 'primary_verify_user' message", function() { - closeCB = expectedClose("primary_verify_user", "email", "unregistered@testuser.com"); - xhr.useResult("primary"); - - dialogHelpers.createUser.call(controllerMock, "unregistered@testuser.com", function(staged) { - equal(staged, true, "user was staged"); - start(); - }); - }); - asyncTest("addEmail happy case", function() { closeCB = expectedClose("email_staged", "email", "unregistered@testuser.com"); dialogHelpers.addEmail.call(controllerMock, "unregistered@testuser.com", function(added) { diff --git a/resources/views/dialog_layout.ejs b/resources/views/dialog_layout.ejs index 52fa963ec..dec481e41 100644 --- a/resources/views/dialog_layout.ejs +++ b/resources/views/dialog_layout.ejs @@ -92,6 +92,8 @@ <script type="text/javascript" src="/dialog/controllers/addemail.js"></script> <script type="text/javascript" src="/dialog/controllers/required_email.js"></script> <script type="text/javascript" src="/dialog/controllers/verify_primary_user.js"></script> + <script type="text/javascript" src="/dialog/controllers/provision_primary_user.js"></script> + <script type="text/javascript" src="/dialog/controllers/email_chosen.js"></script> <script type="text/javascript" src="/dialog/start.js"></script> <% } %> <% } %> diff --git a/scripts/compress.sh b/scripts/compress.sh index 53f0c063c..fafa43e42 100755 --- a/scripts/compress.sh +++ b/scripts/compress.sh @@ -54,7 +54,7 @@ cp templates.js $BUILD_PATH/templates.js cd ../.. # produce the dialog js -cat lib/jquery-1.6.2.min.js lib/winchan.js lib/underscore-min.js lib/vepbundle.js lib/ejs.js shared/browserid.js lib/hub.js lib/dom-jquery.js lib/module.js shared/javascript-extensions.js shared/mediator.js shared/class.js shared/storage.js $BUILD_PATH/templates.js shared/renderer.js shared/error-display.js shared/screens.js shared/tooltip.js shared/validation.js shared/network.js shared/provisioning.js shared/user.js shared/error-messages.js shared/browser-support.js shared/wait-messages.js shared/helpers.js dialog/resources/internal_api.js dialog/resources/helpers.js dialog/resources/state_machine.js dialog/controllers/page.js dialog/controllers/code_check.js dialog/controllers/actions.js dialog/controllers/dialog.js dialog/controllers/authenticate.js dialog/controllers/forgotpassword.js dialog/controllers/checkregistration.js dialog/controllers/pickemail.js dialog/controllers/addemail.js dialog/controllers/required_email.js dialog/start.js > $BUILD_PATH/dialog.uncompressed.js +cat lib/jquery-1.6.2.min.js lib/winchan.js lib/underscore-min.js lib/vepbundle.js lib/ejs.js shared/browserid.js lib/hub.js lib/dom-jquery.js lib/module.js shared/javascript-extensions.js shared/mediator.js shared/class.js shared/storage.js $BUILD_PATH/templates.js shared/renderer.js shared/error-display.js shared/screens.js shared/tooltip.js shared/validation.js shared/network.js shared/provisioning.js shared/user.js shared/error-messages.js shared/browser-support.js shared/wait-messages.js shared/helpers.js dialog/resources/internal_api.js dialog/resources/helpers.js dialog/resources/state_machine.js dialog/controllers/page.js dialog/controllers/code_check.js dialog/controllers/actions.js dialog/controllers/dialog.js dialog/controllers/authenticate.js dialog/controllers/forgotpassword.js dialog/controllers/checkregistration.js dialog/controllers/pickemail.js dialog/controllers/addemail.js dialog/controllers/required_email.js dialog/controllers/verify_primary_user.js dialog/controllers/email_chosen.js dialog/start.js > $BUILD_PATH/dialog.uncompressed.js # produce the dialog css cat css/common.css dialog/css/popup.css dialog/css/m.css > $BUILD_PATH/dialog.uncompressed.css -- GitLab