Bug 1101444 - [Loop] For each invalid inserted verification code, Device receives a valid verification code. r=spenrose

This commit is contained in:
Fernando Jimenez 2014-11-27 11:53:43 +01:00
parent 2db83f625d
commit 9454dd637d
5 changed files with 541 additions and 462 deletions

View File

@ -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();

View File

@ -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);
}

View File

@ -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();

View File

@ -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();
};

View File

@ -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]