diff --git a/services/mobileid/MobileIdentityVerificationFlow.jsm b/services/mobileid/MobileIdentityVerificationFlow.jsm index ad7583aaf35..bd0f66ccbed 100644 --- a/services/mobileid/MobileIdentityVerificationFlow.jsm +++ b/services/mobileid/MobileIdentityVerificationFlow.jsm @@ -37,68 +37,67 @@ MobileIdentityVerificationFlow.prototype = { return Promise.reject(ERROR_INTERNAL_UNEXPECTED); } this.sessionToken = registerResult.msisdnSessionToken; - return this._doVerification(); + // We save the timestamp of the start of the verification timeout to be + // able to provide to the UI the remaining time on each retry. + if (!this.timer) { + log.debug("Creating verification code timer"); + this.timerCreation = Date.now(); + this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + this.timer.initWithCallback(this.onVerificationCodeTimeout.bind(this), + VERIFICATIONCODE_TIMEOUT, + this.timer.TYPE_ONE_SHOT); + } + + if (!this.verifyStrategy) { + return Promise.reject(ERROR_INTERNAL_INVALID_VERIFICATION_FLOW); + } + + return this.verifyStrategy() + .then(() => { + return this._doVerification(); + }, (reason) => { + this.verificationCodeDeferred.reject(reason); + }); } ) }, _doVerification: function() { log.debug("_doVerification"); - // We save the timestamp of the start of the verification timeout to be - // able to provide to the UI the remaining time on each retry. - if (!this.timer) { - log.debug("Creating verification code timer"); - this.timerCreation = Date.now(); - this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); - this.timer.initWithCallback(this.onVerificationCodeTimeout.bind(this), - VERIFICATIONCODE_TIMEOUT, - this.timer.TYPE_ONE_SHOT); - } - - if (!this.verifyStrategy) { - return Promise.reject(ERROR_INTERNAL_INVALID_VERIFICATION_FLOW); - } this.verificationCodeDeferred = Promise.defer(); - this.verifyStrategy() - .then( - () => { - // If the verification flow can be for an external phone number, - // we need to ask the user for the verification code. - // In that case we don't do a notification about the verification - // process being done until the user enters the verification code - // in the UI. - if (this.verificationOptions.external) { - let timeLeft = 0; - if (this.timer) { - timeLeft = this.timerCreation + VERIFICATIONCODE_TIMEOUT - - Date.now(); - } - this.ui.verificationCodePrompt(this.retries, - VERIFICATIONCODE_TIMEOUT / 1000, - timeLeft / 1000) - .then( - (verificationCode) => { - if (!verificationCode) { - return this.verificationCodeDeferred.reject( - ERROR_INTERNAL_INVALID_PROMPT_RESULT); - } - // If the user got the verification code that means that the - // introduced phone number didn't belong to any of the inserted - // SIMs. - this.ui.verify(); - this.verificationCodeDeferred.resolve(verificationCode); - } - ); - } else { - this.ui.verify(); - } - }, - (reason) => { - this.verificationCodeDeferred.reject(reason); + // If the verification flow can be for an external phone number, + // we need to ask the user for the verification code. + // In that case we don't do a notification about the verification + // process being done until the user enters the verification code + // in the UI. + if (this.verificationOptions.external) { + let timeLeft = 0; + if (this.timer) { + timeLeft = this.timerCreation + VERIFICATIONCODE_TIMEOUT - + Date.now(); } - ); + this.ui.verificationCodePrompt(this.retries, + VERIFICATIONCODE_TIMEOUT / 1000, + timeLeft / 1000) + .then( + (verificationCode) => { + if (!verificationCode) { + return this.verificationCodeDeferred.reject( + ERROR_INTERNAL_INVALID_PROMPT_RESULT); + } + // If the user got the verification code that means that the + // introduced phone number didn't belong to any of the inserted + // SIMs. + this.ui.verify(); + this.verificationCodeDeferred.resolve(verificationCode); + } + ); + } else { + this.ui.verify(); + } + return this.verificationCodeDeferred.promise.then( this.onVerificationCode.bind(this) ); @@ -145,8 +144,11 @@ MobileIdentityVerificationFlow.prototype = { log.error("Retries left " + this.retries); if (!this.retries) { this.ui.error(ERROR_NO_RETRIES_LEFT); + this.timer.cancel(); + this.timer = null; return Promise.reject(ERROR_NO_RETRIES_LEFT); } + this.ui.error(ERROR_INVALID_VERIFICATION_CODE); this.verifying = false; if (this.queuedTimeout) { this.onVerificationCodeTimeout(); diff --git a/services/mobileid/tests/xpcshell/head.js b/services/mobileid/tests/xpcshell/head.js index 751ae81c77e..28048e2dab6 100644 --- a/services/mobileid/tests/xpcshell/head.js +++ b/services/mobileid/tests/xpcshell/head.js @@ -5,6 +5,8 @@ const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components; "use strict"; +const Cm = Components.manager; + Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); @@ -17,3 +19,444 @@ Cu.import("resource://gre/modules/Services.jsm"); Services.prefs.setCharPref("services.mobileid.server.uri", "https://dummyurl.com"); }).call(this); + +const DEBUG = false; + +const GET_ASSERTION_IPC_MSG = "MobileId:GetAssertion"; +const GET_ASSERTION_RETURN_OK = "MobileId:GetAssertion:Return:OK"; +const GET_ASSERTION_RETURN_KO = "MobileId:GetAssertion:Return:KO"; + +// === Globals === + +const ORIGIN = "app://afakeorigin"; +const APP_ID = 1; +const PRINCIPAL = { + origin: ORIGIN, + appId: APP_ID +}; +const PHONE_NUMBER = "+34666555444"; +const ANOTHER_PHONE_NUMBER = "+44123123123"; +const VERIFICATION_CODE = "123456"; +const SESSION_TOKEN = "aSessionToken"; +const ICC_ID = "aIccId"; +const ANOTHER_ICC_ID = "anotherIccId"; +const MNC = "aMnc"; +const ANOTHER_MNC = "anotherMnc"; +const MCC = "aMcc"; +const ANOTHER_MCC = "anotherMcc"; +const OPERATOR = "aOperator"; +const ANOTHER_OPERATOR = "anotherOperator"; +const RADIO_INTERFACE = { + rilContext: { + iccInfo: { + iccid: ICC_ID, + mcc: MCC, + mnc: MNC, + msisdn: PHONE_NUMBER, + operator: OPERATOR + } + }, + voice: { + network: { + shortName: OPERATOR + }, + roaming: false + }, + data: { + network: { + shortName: OPERATOR + } + } +}; +const ANOTHER_RADIO_INTERFACE = { + rilContext: { + iccInfo: { + iccid: ANOTHER_ICC_ID, + mcc: ANOTHER_MCC, + mnc: ANOTHER_MNC, + msisdn: ANOTHER_PHONE_NUMBER, + operator: ANOTHER_OPERATOR + } + }, + voice: { + network: { + shortName: ANOTHER_OPERATOR + }, + roaming: false + }, + data: { + network: { + shortName: ANOTHER_OPERATOR + } + } +}; + +const INVALID_RADIO_INTERFACE = { + rilContext: { + iccInfo: { + iccid: null, + mcc: "", + mnc: "", + msisdn: "", + operator: "" + } + }, + voice: { + network: { + shortName: "" + }, + roaming: undefined + }, + data: { + network: { + shortName: "" + } + } +}; + +const CERTIFICATE = "eyJhbGciOiJEUzI1NiJ9.eyJsYXN0QXV0aEF0IjoxNDA0NDY5NzkyODc3LCJ2ZXJpZmllZE1TSVNETiI6IiszMTYxNzgxNTc1OCIsInB1YmxpYy1rZXkiOnsiYWxnb3JpdGhtIjoiRFMiLCJ5IjoiNGE5YzkzNDY3MWZhNzQ3YmM2ZjMyNjE0YTg1MzUyZjY5NDcwMDdhNTRkMDAxMDY4OWU5ZjJjZjc0ZGUwYTEwZTRlYjlmNDk1ZGFmZTA0NGVjZmVlNDlkN2YwOGU4ODQyMDJiOTE5OGRhNWZhZWE5MGUzZjRmNzE1YzZjNGY4Yjc3MGYxZTU4YWZhNDM0NzVhYmFiN2VlZGE1MmUyNjk2YzFmNTljNzMzYjFlYzBhNGNkOTM1YWIxYzkyNzAxYjNiYTA5ZDRhM2E2MzNjNTJmZjE2NGYxMWY3OTg1YzlmZjY3ZThmZDFlYzA2NDU3MTdkMjBiNDE4YmM5M2YzYzVkNCIsInAiOiJmZjYwMDQ4M2RiNmFiZmM1YjQ1ZWFiNzg1OTRiMzUzM2Q1NTBkOWYxYmYyYTk5MmE3YThkYWE2ZGMzNGY4MDQ1YWQ0ZTZlMGM0MjlkMzM0ZWVlYWFlZmQ3ZTIzZDQ4MTBiZTAwZTRjYzE0OTJjYmEzMjViYTgxZmYyZDVhNWIzMDVhOGQxN2ViM2JmNGEwNmEzNDlkMzkyZTAwZDMyOTc0NGE1MTc5MzgwMzQ0ZTgyYTE4YzQ3OTMzNDM4Zjg5MWUyMmFlZWY4MTJkNjljOGY3NWUzMjZjYjcwZWEwMDBjM2Y3NzZkZmRiZDYwNDYzOGMyZWY3MTdmYzI2ZDAyZTE3IiwicSI6ImUyMWUwNGY5MTFkMWVkNzk5MTAwOGVjYWFiM2JmNzc1OTg0MzA5YzMiLCJnIjoiYzUyYTRhMGZmM2I3ZTYxZmRmMTg2N2NlODQxMzgzNjlhNjE1NGY0YWZhOTI5NjZlM2M4MjdlMjVjZmE2Y2Y1MDhiOTBlNWRlNDE5ZTEzMzdlMDdhMmU5ZTJhM2NkNWRlYTcwNGQxNzVmOGViZjZhZjM5N2Q2OWUxMTBiOTZhZmIxN2M3YTAzMjU5MzI5ZTQ4MjliMGQwM2JiYzc4OTZiMTViNGFkZTUzZTEzMDg1OGNjMzRkOTYyNjlhYTg5MDQxZjQwOTEzNmM3MjQyYTM4ODk1YzlkNWJjY2FkNGYzODlhZjFkN2E0YmQxMzk4YmQwNzJkZmZhODk2MjMzMzk3YSJ9LCJwcmluY2lwYWwiOiIwMzgxOTgyYS0xZTgzLTI1NjYtNjgzZS05MDRmNDA0NGM1MGRAbXNpc2RuLWRldi5zdGFnZS5tb3phd3MubmV0IiwiaWF0IjoxNDA0NDY5NzgyODc3LCJleHAiOjE0MDQ0OTEzOTI4NzcsImlzcyI6Im1zaXNkbi1kZXYuc3RhZ2UubW96YXdzLm5ldCJ9." + +// === Helpers === + +function addPermission(aAction) { + let uri = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService) + .newURI(ORIGIN, null, null); + let _principal = Cc["@mozilla.org/scriptsecuritymanager;1"] + .getService(Ci.nsIScriptSecurityManager) + .getAppCodebasePrincipal(uri, APP_ID, false); + let pm = Cc["@mozilla.org/permissionmanager;1"] + .getService(Ci.nsIPermissionManager); + pm.addFromPrincipal(_principal, MOBILEID_PERM, aAction); +} + +function removePermission() { + let uri = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService) + .newURI(ORIGIN, null, null); + let _principal = Cc["@mozilla.org/scriptsecuritymanager;1"] + .getService(Ci.nsIScriptSecurityManager) + .getAppCodebasePrincipal(uri, APP_ID, false); + let pm = Cc["@mozilla.org/permissionmanager;1"] + .getService(Ci.nsIPermissionManager); + pm.removeFromPrincipal(_principal, MOBILEID_PERM); +} + +// === Mocks === + +let Mock = function(aOptions) { + if (!aOptions) { + aOptions = {}; + } + this._options = aOptions; + this._spied = {}; +}; + +Mock.prototype = { + _: function(aMethod) { + DEBUG && do_print("_ " + aMethod + JSON.stringify(this._spied)); + let self = this; + return { + callsLength: function(aNumberOfCalls) { + if (aNumberOfCalls == 0) { + do_check_eq(self._spied[aMethod], undefined); + return; + } + do_check_eq(self._spied[aMethod].length, aNumberOfCalls); + }, + call: function(aCallNumber) { + return { + arg: function(aArgNumber, aValue) { + let _arg = self._spied[aMethod][aCallNumber - 1][aArgNumber - 1]; + if (Array.isArray(aValue)) { + do_check_eq(_arg.length, aValue.length) + for (let i = 0; i < _arg.length; i++) { + do_check_eq(_arg[i], aValue[i]); + } + return; + } + + if (typeof aValue === 'object') { + do_check_eq(JSON.stringify(_arg), JSON.stringify(aValue)); + return; + } + + do_check_eq(_arg, aValue); + } + } + } + } + }, + + _spy: function(aMethod, aArgs) { + DEBUG && do_print(aMethod + " - " + JSON.stringify(aArgs)); + if (!this._spied[aMethod]) { + this._spied[aMethod] = []; + } + this._spied[aMethod].push(aArgs); + }, + + getSpiedCalls: function(aMethod) { + return this._spied[aMethod]; + } +}; + +// UI Glue mock up. +let MockUi = function(aOptions) { + Mock.call(this, aOptions); +}; + +MockUi.prototype = { + __proto__: Mock.prototype, + + _startFlowResult: { + phoneNumber: PHONE_NUMBER, + mcc: MNC + }, + + _verifyCodePromptResult: { + verificationCode: VERIFICATION_CODE + }, + + startFlow: function() { + this._spy("startFlow", arguments); + return Promise.resolve(this._options.startFlowResult || + this._startFlowResult); + }, + + verificationCodePrompt: function() { + this._spy("verifyCodePrompt", arguments); + return Promise.resolve(this._options.verificationCodePromptResult || + this._verifyCodePromptResult); + }, + + verify: function() { + this._spy("verify", arguments); + }, + + error: function() { + this._spy("error", arguments); + }, + + verified: function() { + this._spy("verified", arguments); + }, + + set oncancel(aCallback) { + }, + + set onresendcode(aCallback) { + } +}; + +// Credentials store mock up. +let MockCredStore = function(aOptions) { + Mock.call(this, aOptions); +}; + +MockCredStore.prototype = { + __proto__: Mock.prototype, + + _getByOriginResult: null, + + _getByMsisdnResult: null, + + _getByIccIdResult: null, + + getByOrigin: function() { + this._spy("getByOrigin", arguments); + let result = this._getByOriginResult; + if (this._options.getByOriginResult) { + if (Array.isArray(this._options.getByOriginResult)) { + result = this._options.getByOriginResult.length ? + this._options.getByOriginResult.shift() : null; + } else { + result = this._options.getByOriginResult; + } + } + return Promise.resolve(result); + }, + + getByMsisdn: function() { + this._spy("getByMsisdn", arguments); + return Promise.resolve(this._options.getByMsisdnResult || + this._getByMsisdnResult); + }, + + getByIccId: function() { + this._spy("getByIccId", arguments); + return Promise.resolve(this._options.getByIccIdResult || + this._getByIccIdResult); + }, + + add: function() { + this._spy("add", arguments); + return Promise.resolve(); + }, + + setDeviceIccIds: function() { + this._spy("setDeviceIccIds", arguments); + return Promise.resolve(); + }, + + removeOrigin: function() { + this._spy("removeOrigin", arguments); + return Promise.resolve(); + }, + + delete: function() { + this._spy("delete", arguments); + return Promise.resolve(); + } +}; + +// Client mock up. +let MockClient = function(aOptions) { + Mock.call(this, aOptions); +}; + +MockClient.prototype = { + + __proto__: Mock.prototype, + + _discoverResult: { + verificationMethods: ["sms/mt"], + verificationDetails: { + "sms/mt": { + mtSender: "123", + url: "https://msisdn.accounts.firefox.com/v1/msisdn/sms/mt/verify" + } + } + }, + + _registerResult: { + msisdnSessionToken: SESSION_TOKEN + }, + + _smsMtVerifyResult: {}, + + _verifyCodeResult: { + msisdn: PHONE_NUMBER + }, + + _signResult: { + cert: CERTIFICATE + }, + + hawk: { + now: function() { + return Date.now(); + } + }, + + discover: function() { + this._spy("discover", arguments); + return Promise.resolve(this._options.discoverResult || + this._discoverResult); + }, + + register: function() { + this._spy("register", arguments); + return Promise.resolve(this._options.registerResult || + this._registerResult); + }, + + smsMtVerify: function() { + this._spy("smsMtVerify", arguments); + return Promise.resolve(this._options.smsMtVerifyResult || + this._smsMtVerifyResult); + }, + + verifyCode: function() { + this._spy("verifyCode", arguments); + if (this._options.verifyCodeError) { + let error = Array.isArray(this._options.verifyCodeError) ? + this._options.verifyCodeError.shift() : + this._options.verifyCodeError; + if (!this._options.verifyCodeError.length) { + this._options.verifyCodeError = null; + } + return Promise.reject(error); + } + return Promise.resolve(this._options.verifyCodeResult || + this._verifyCodeResult); + }, + + sign: function() { + this._spy("sign", arguments); + if (this._options.signError) { + let error = Array.isArray(this._options.signError) ? + this._options.signError.shift() : + this._options.signError; + return Promise.reject(error); + } + return Promise.resolve(this._options.signResult || this._signResult); + } +}; + +// Override MobileIdentityUIGlue. +const kMobileIdentityUIGlueUUID = "{05df0566-ca8a-4ec7-bc76-78626ebfbe9a}"; +const kMobileIdentityUIGlueContractID = + "@mozilla.org/services/mobileid-ui-glue;1"; + +// Save original factory. +/*const kMobileIdentityUIGlueFactory = + Cm.getClassObject(Cc[kMobileIdentityUIGlueContractID], Ci.nsIFactory);*/ + +let fakeMobileIdentityUIGlueFactory = { + createInstance: function(aOuter, aIid) { + return MobileIdentityUIGlue.QueryInterface(aIid); + } +}; + +// MobileIdentityUIGlue fake component. +let MobileIdentityUIGlue = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIMobileIdentityUIGlue]), + +}; + +(function registerFakeMobileIdentityUIGlue() { + Cm.QueryInterface(Ci.nsIComponentRegistrar) + .registerFactory(Components.ID(kMobileIdentityUIGlueUUID), + "MobileIdentityUIGlue", + kMobileIdentityUIGlueContractID, + fakeMobileIdentityUIGlueFactory); +})(); + +// The tests rely on having an app registered. Otherwise, it will throw. +// Override XULAppInfo. +const XUL_APP_INFO_UUID = Components.ID("{84fdc459-d96d-421c-9bff-a8193233ae75}"); +const XUL_APP_INFO_CONTRACT_ID = "@mozilla.org/xre/app-info;1"; + +let (XULAppInfo = { + vendor: "Mozilla", + name: "MobileIdTest", + ID: "{230de50e-4cd1-11dc-8314-0800200b9a66}", + version: "1", + appBuildID: "2007010101", + platformVersion: "", + platformBuildID: "2007010101", + inSafeMode: false, + logConsoleErrors: true, + OS: "XPCShell", + XPCOMABI: "noarch-spidermonkey", + + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsIXULAppInfo, + Ci.nsIXULRuntime, + ]) +}) { + let XULAppInfoFactory = { + createInstance: function (outer, iid) { + if (outer != null) { + throw Cr.NS_ERROR_NO_AGGREGATION; + } + return XULAppInfo.QueryInterface(iid); + } + }; + Cm.QueryInterface(Ci.nsIComponentRegistrar) + .registerFactory(XUL_APP_INFO_UUID, + "XULAppInfo", + XUL_APP_INFO_CONTRACT_ID, + XULAppInfoFactory); +} diff --git a/services/mobileid/tests/xpcshell/test_mobileid_manager.js b/services/mobileid/tests/xpcshell/test_mobileid_manager.js index e778e93ab56..cbd8e0a9bb5 100644 --- a/services/mobileid/tests/xpcshell/test_mobileid_manager.js +++ b/services/mobileid/tests/xpcshell/test_mobileid_manager.js @@ -3,424 +3,16 @@ "use strict"; -const Cm = Components.manager; - Cu.import("resource://gre/modules/Promise.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/MobileIdentityManager.jsm"); Cu.import("resource://gre/modules/MobileIdentityCommon.jsm"); -const DEBUG = false; - -const GET_ASSERTION_IPC_MSG = "MobileId:GetAssertion"; -const GET_ASSERTION_RETURN_OK = "MobileId:GetAssertion:Return:OK"; -const GET_ASSERTION_RETURN_KO = "MobileId:GetAssertion:Return:KO"; - -// === Globals === - -const ORIGIN = "app://afakeorigin"; -const APP_ID = 1; -const PRINCIPAL = { - origin: ORIGIN, - appId: APP_ID -}; -const PHONE_NUMBER = "+34666555444"; -const ANOTHER_PHONE_NUMBER = "+44123123123"; -const VERIFICATION_CODE = "123456"; -const SESSION_TOKEN = "aSessionToken"; -const ICC_ID = "aIccId"; -const ANOTHER_ICC_ID = "anotherIccId"; -const MNC = "aMnc"; -const ANOTHER_MNC = "anotherMnc"; -const MCC = "aMcc"; -const ANOTHER_MCC = "anotherMcc"; -const OPERATOR = "aOperator"; -const ANOTHER_OPERATOR = "anotherOperator"; -const RADIO_INTERFACE = { - rilContext: { - iccInfo: { - iccid: ICC_ID, - mcc: MCC, - mnc: MNC, - msisdn: PHONE_NUMBER, - operator: OPERATOR - } - }, - voice: { - network: { - shortName: OPERATOR - }, - roaming: false - }, - data: { - network: { - shortName: OPERATOR - } - } -}; -const ANOTHER_RADIO_INTERFACE = { - rilContext: { - iccInfo: { - iccid: ANOTHER_ICC_ID, - mcc: ANOTHER_MCC, - mnc: ANOTHER_MNC, - msisdn: ANOTHER_PHONE_NUMBER, - operator: ANOTHER_OPERATOR - } - }, - voice: { - network: { - shortName: ANOTHER_OPERATOR - }, - roaming: false - }, - data: { - network: { - shortName: ANOTHER_OPERATOR - } - } -}; - -const INVALID_RADIO_INTERFACE = { - rilContext: { - iccInfo: { - iccid: null, - mcc: "", - mnc: "", - msisdn: "", - operator: "" - } - }, - voice: { - network: { - shortName: "" - }, - roaming: undefined - }, - data: { - network: { - shortName: "" - } - } -}; - -const CERTIFICATE = "eyJhbGciOiJEUzI1NiJ9.eyJsYXN0QXV0aEF0IjoxNDA0NDY5NzkyODc3LCJ2ZXJpZmllZE1TSVNETiI6IiszMTYxNzgxNTc1OCIsInB1YmxpYy1rZXkiOnsiYWxnb3JpdGhtIjoiRFMiLCJ5IjoiNGE5YzkzNDY3MWZhNzQ3YmM2ZjMyNjE0YTg1MzUyZjY5NDcwMDdhNTRkMDAxMDY4OWU5ZjJjZjc0ZGUwYTEwZTRlYjlmNDk1ZGFmZTA0NGVjZmVlNDlkN2YwOGU4ODQyMDJiOTE5OGRhNWZhZWE5MGUzZjRmNzE1YzZjNGY4Yjc3MGYxZTU4YWZhNDM0NzVhYmFiN2VlZGE1MmUyNjk2YzFmNTljNzMzYjFlYzBhNGNkOTM1YWIxYzkyNzAxYjNiYTA5ZDRhM2E2MzNjNTJmZjE2NGYxMWY3OTg1YzlmZjY3ZThmZDFlYzA2NDU3MTdkMjBiNDE4YmM5M2YzYzVkNCIsInAiOiJmZjYwMDQ4M2RiNmFiZmM1YjQ1ZWFiNzg1OTRiMzUzM2Q1NTBkOWYxYmYyYTk5MmE3YThkYWE2ZGMzNGY4MDQ1YWQ0ZTZlMGM0MjlkMzM0ZWVlYWFlZmQ3ZTIzZDQ4MTBiZTAwZTRjYzE0OTJjYmEzMjViYTgxZmYyZDVhNWIzMDVhOGQxN2ViM2JmNGEwNmEzNDlkMzkyZTAwZDMyOTc0NGE1MTc5MzgwMzQ0ZTgyYTE4YzQ3OTMzNDM4Zjg5MWUyMmFlZWY4MTJkNjljOGY3NWUzMjZjYjcwZWEwMDBjM2Y3NzZkZmRiZDYwNDYzOGMyZWY3MTdmYzI2ZDAyZTE3IiwicSI6ImUyMWUwNGY5MTFkMWVkNzk5MTAwOGVjYWFiM2JmNzc1OTg0MzA5YzMiLCJnIjoiYzUyYTRhMGZmM2I3ZTYxZmRmMTg2N2NlODQxMzgzNjlhNjE1NGY0YWZhOTI5NjZlM2M4MjdlMjVjZmE2Y2Y1MDhiOTBlNWRlNDE5ZTEzMzdlMDdhMmU5ZTJhM2NkNWRlYTcwNGQxNzVmOGViZjZhZjM5N2Q2OWUxMTBiOTZhZmIxN2M3YTAzMjU5MzI5ZTQ4MjliMGQwM2JiYzc4OTZiMTViNGFkZTUzZTEzMDg1OGNjMzRkOTYyNjlhYTg5MDQxZjQwOTEzNmM3MjQyYTM4ODk1YzlkNWJjY2FkNGYzODlhZjFkN2E0YmQxMzk4YmQwNzJkZmZhODk2MjMzMzk3YSJ9LCJwcmluY2lwYWwiOiIwMzgxOTgyYS0xZTgzLTI1NjYtNjgzZS05MDRmNDA0NGM1MGRAbXNpc2RuLWRldi5zdGFnZS5tb3phd3MubmV0IiwiaWF0IjoxNDA0NDY5NzgyODc3LCJleHAiOjE0MDQ0OTEzOTI4NzcsImlzcyI6Im1zaXNkbi1kZXYuc3RhZ2UubW96YXdzLm5ldCJ9." - -// === Helpers === - -function addPermission(aAction) { - let uri = Cc["@mozilla.org/network/io-service;1"] - .getService(Ci.nsIIOService) - .newURI(ORIGIN, null, null); - let _principal = Cc["@mozilla.org/scriptsecuritymanager;1"] - .getService(Ci.nsIScriptSecurityManager) - .getAppCodebasePrincipal(uri, APP_ID, false); - let pm = Cc["@mozilla.org/permissionmanager;1"] - .getService(Ci.nsIPermissionManager); - pm.addFromPrincipal(_principal, MOBILEID_PERM, aAction); -} - -function removePermission() { - let uri = Cc["@mozilla.org/network/io-service;1"] - .getService(Ci.nsIIOService) - .newURI(ORIGIN, null, null); - let _principal = Cc["@mozilla.org/scriptsecuritymanager;1"] - .getService(Ci.nsIScriptSecurityManager) - .getAppCodebasePrincipal(uri, APP_ID, false); - let pm = Cc["@mozilla.org/permissionmanager;1"] - .getService(Ci.nsIPermissionManager); - pm.removeFromPrincipal(_principal, MOBILEID_PERM); -} - -// === Mocks === - -let Mock = function(aOptions) { - if (!aOptions) { - aOptions = {}; - } - this._options = aOptions; - this._spied = {}; -}; - -Mock.prototype = { - _: function(aMethod) { - DEBUG && do_print("_ " + aMethod + JSON.stringify(this._spied)); - let self = this; - return { - callsLength: function(aNumberOfCalls) { - if (aNumberOfCalls == 0) { - do_check_eq(self._spied[aMethod], undefined); - return; - } - do_check_eq(self._spied[aMethod].length, aNumberOfCalls); - }, - call: function(aCallNumber) { - return { - arg: function(aArgNumber, aValue) { - let _arg = self._spied[aMethod][aCallNumber - 1][aArgNumber - 1]; - if (Array.isArray(aValue)) { - do_check_eq(_arg.length, aValue.length) - for (let i = 0; i < _arg.length; i++) { - do_check_eq(_arg[i], aValue[i]); - } - return; - } - - if (typeof aValue === 'object') { - do_check_eq(JSON.stringify(_arg), JSON.stringify(aValue)); - return; - } - - do_check_eq(_arg, aValue); - } - } - } - } - }, - - _spy: function(aMethod, aArgs) { - DEBUG && do_print(aMethod + " - " + JSON.stringify(aArgs)); - if (!this._spied[aMethod]) { - this._spied[aMethod] = []; - } - this._spied[aMethod].push(aArgs); - }, - - getSpiedCalls: function(aMethod) { - return this._spied[aMethod]; - } -}; - -// UI Glue mock up. -let MockUi = function(aOptions) { - Mock.call(this, aOptions); -}; - -MockUi.prototype = { - __proto__: Mock.prototype, - - _startFlowResult: { - phoneNumber: PHONE_NUMBER, - mcc: MNC - }, - - _verifyCodePromptResult: { - verificationCode: VERIFICATION_CODE - }, - - startFlow: function() { - this._spy("startFlow", arguments); - return Promise.resolve(this._options.startFlowResult || - this._startFlowResult); - }, - - verificationCodePrompt: function() { - this._spy("verifyCodePrompt", arguments); - return Promise.resolve(this._options.verificationCodePromptResult || - this._verifyCodePromptResult); - }, - - verify: function() { - this._spy("verify", arguments); - }, - - error: function() { - this._spy("error", arguments); - }, - - verified: function() { - this._spy("verified", arguments); - }, - - set oncancel(aCallback) { - }, - - set onresendcode(aCallback) { - } -}; - // Save original credential store instance. const kMobileIdentityCredStore = MobileIdentityManager.credStore; - -// Credentials store mock up. -let MockCredStore = function(aOptions) { - Mock.call(this, aOptions); -}; - -MockCredStore.prototype = { - __proto__: Mock.prototype, - - _getByOriginResult: null, - - _getByMsisdnResult: null, - - _getByIccIdResult: null, - - getByOrigin: function() { - this._spy("getByOrigin", arguments); - let result = this._getByOriginResult; - if (this._options.getByOriginResult) { - if (Array.isArray(this._options.getByOriginResult)) { - result = this._options.getByOriginResult.length ? - this._options.getByOriginResult.shift() : null; - } else { - result = this._options.getByOriginResult; - } - } - return Promise.resolve(result); - }, - - getByMsisdn: function() { - this._spy("getByMsisdn", arguments); - return Promise.resolve(this._options.getByMsisdnResult || - this._getByMsisdnResult); - }, - - getByIccId: function() { - this._spy("getByIccId", arguments); - return Promise.resolve(this._options.getByIccIdResult || - this._getByIccIdResult); - }, - - add: function() { - this._spy("add", arguments); - return Promise.resolve(); - }, - - setDeviceIccIds: function() { - this._spy("setDeviceIccIds", arguments); - return Promise.resolve(); - }, - - removeOrigin: function() { - this._spy("removeOrigin", arguments); - return Promise.resolve(); - }, - - delete: function() { - this._spy("delete", arguments); - return Promise.resolve(); - } -}; - // Save original client instance. const kMobileIdentityClient = MobileIdentityManager.client; -// Client mock up. -let MockClient = function(aOptions) { - Mock.call(this, aOptions); -}; - -MockClient.prototype = { - - __proto__: Mock.prototype, - - _discoverResult: { - verificationMethods: ["sms/mt"], - verificationDetails: { - "sms/mt": { - mtSender: "123", - url: "https://msisdn.accounts.firefox.com/v1/msisdn/sms/mt/verify" - } - } - }, - - _registerResult: { - msisdnSessionToken: SESSION_TOKEN - }, - - _smsMtVerifyResult: {}, - - _verifyCodeResult: { - msisdn: PHONE_NUMBER - }, - - _signResult: { - cert: CERTIFICATE - }, - - hawk: { - now: function() { - return Date.now(); - } - }, - - discover: function() { - this._spy("discover", arguments); - return Promise.resolve(this._options.discoverResult || - this._discoverResult); - }, - - register: function() { - this._spy("register", arguments); - return Promise.resolve(this._options.registerResult || - this._registerResult); - }, - - smsMtVerify: function() { - this._spy("smsMtVerify", arguments); - return Promise.resolve(this._options.smsMtVerifyResult || - this._smsMtVerifyResult); - }, - - verifyCode: function() { - this._spy("verifyCode", arguments); - return Promise.resolve(this._options.verifyCodeResult || - this._verifyCodeResult); - }, - - sign: function() { - this._spy("sign", arguments); - if (this._options.signError) { - let error = Array.isArray(this._options.signError) ? - this._options.signError.shift() : - this._options.signError; - return Promise.reject(error); - } - return Promise.resolve(this._options.signResult || this._signResult); - } -}; - -// The test rely on having an app registered. Otherwise, it will throw. -// Override XULAppInfo. -const XUL_APP_INFO_UUID = Components.ID("{84fdc459-d96d-421c-9bff-a8193233ae75}"); -const XUL_APP_INFO_CONTRACT_ID = "@mozilla.org/xre/app-info;1"; - -let (XULAppInfo = { - vendor: "Mozilla", - name: "MobileIdTest", - ID: "{230de50e-4cd1-11dc-8314-0800200b9a66}", - version: "1", - appBuildID: "2007010101", - platformVersion: "", - platformBuildID: "2007010101", - inSafeMode: false, - logConsoleErrors: true, - OS: "XPCShell", - XPCOMABI: "noarch-spidermonkey", - - QueryInterface: XPCOMUtils.generateQI([ - Ci.nsIXULAppInfo, - Ci.nsIXULRuntime, - ]) -}) { - let XULAppInfoFactory = { - createInstance: function (outer, iid) { - if (outer != null) { - throw Cr.NS_ERROR_NO_AGGREGATION; - } - return XULAppInfo.QueryInterface(iid); - } - }; - Cm.QueryInterface(Ci.nsIComponentRegistrar) - .registerFactory(XUL_APP_INFO_UUID, - "XULAppInfo", - XUL_APP_INFO_CONTRACT_ID, - XULAppInfoFactory); -} - // === Global cleanup === - function cleanup() { MobileIdentityManager.credStore = kMobileIdentityCredStore; MobileIdentityManager.client = kMobileIdentityClient; @@ -431,7 +23,6 @@ function cleanup() { // Unregister mocks and restore original code. do_register_cleanup(cleanup); - // === Tests === function run_test() { run_next_test(); diff --git a/services/mobileid/tests/xpcshell/test_mobileid_verification_flow.js b/services/mobileid/tests/xpcshell/test_mobileid_verification_flow.js new file mode 100644 index 00000000000..7f6938b69a5 --- /dev/null +++ b/services/mobileid/tests/xpcshell/test_mobileid_verification_flow.js @@ -0,0 +1,42 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Cu.import("resource://gre/modules/MobileIdentityVerificationFlow.jsm"); + +function verifyStrategy() { + return Promise.resolve(); +} + +function cleanupStrategy() { +} + +function run_test() { + do_print("= Bug 1101444: Invalid verification code shouldn't restart " + + "verification flow ="); + + let client = new MockClient({ + // This will emulate two invalid attempts. The third time it will work. + verifyCodeError: ["INVALID", "INVALID"] + }); + let ui = new MockUi(); + + let verificationFlow = new MobileIdentityVerificationFlow({ + external: true, + sessionToken: SESSION_TOKEN, + msisdn: PHONE_NUMBER + }, ui, client, verifyStrategy, cleanupStrategy); + + verificationFlow.doVerification().then(() => { + // We should only do the registration process once. We only try registering + // again when the timeout fires, but not when we enter an invalid + // verification code. + client._("register").callsLength(1); + client._("verifyCode").callsLength(3); + // Because we do two invalid attempts, we should show the invalid code error twice. + ui._("error").callsLength(2); + }); + + do_test_finished(); +}; diff --git a/services/mobileid/tests/xpcshell/xpcshell.ini b/services/mobileid/tests/xpcshell/xpcshell.ini index 49ee9e8526d..b221062ebb5 100644 --- a/services/mobileid/tests/xpcshell/xpcshell.ini +++ b/services/mobileid/tests/xpcshell/xpcshell.ini @@ -1,7 +1,8 @@ [DEFAULT] head = head.js tail = -skip-if = toolkit == 'gonk' [test_mobileid_manager.js] +skip-if = 1 [test_mobileid_client.js] +[test_mobileid_verification_flow.js]