diff --git a/services/mobileid/MobileIdentityManager.jsm b/services/mobileid/MobileIdentityManager.jsm index 485305b62618..3c3556fcc2b5 100644 --- a/services/mobileid/MobileIdentityManager.jsm +++ b/services/mobileid/MobileIdentityManager.jsm @@ -52,15 +52,15 @@ XPCOMUtils.defineLazyServiceGetter(this, "appsService", "nsIAppsService"); #ifdef MOZ_B2G_RIL -XPCOMUtils.defineLazyServiceGetter(this, "gRil", +XPCOMUtils.defineLazyServiceGetter(this, "Ril", "@mozilla.org/ril;1", "nsIRadioInterfaceLayer"); -XPCOMUtils.defineLazyServiceGetter(this, "iccProvider", +XPCOMUtils.defineLazyServiceGetter(this, "IccProvider", "@mozilla.org/ril/content-helper;1", "nsIIccProvider"); -XPCOMUtils.defineLazyServiceGetter(this, "mobileConnectionService", +XPCOMUtils.defineLazyServiceGetter(this, "MobileConnectionService", "@mozilla.org/mobileconnection/mobileconnectionservice;1", "nsIMobileConnectionService"); #endif @@ -104,30 +104,84 @@ this.MobileIdentityManager = { this.messageManagers = null; }, - /********************************************************* * Getters ********************************************************/ +#ifdef MOZ_B2G_RIL + // We have these getters to allow mocking RIL stuff from the tests. + get ril() { + if (this._ril) { + return this._ril; + } + return Ril; + }, + + get iccProvider() { + if (this._iccProvider) { + return this._iccProvider; + } + return IccProvider; + }, + + get mobileConnectionService() { + if (this._mobileConnectionService) { + return this._mobileConnectionService; + } + return MobileConnectionService; + }, +#endif get iccInfo() { if (this._iccInfo) { return this._iccInfo; } #ifdef MOZ_B2G_RIL + let self = this; + let iccListener = { + notifyStkCommand: function() {}, + + notifyStkSessionEnd: function() {}, + + notifyCardStateChanged: function() {}, + + notifyIccInfoChanged: function() { + // If we receive a notification about an ICC info change, we clear + // the ICC related caches so they can be rebuilt with the new changes. + + log.debug("ICC info changed observed. Clearing caches"); + + // We don't need to keep listening for changes until we rebuild the + // cache again. + for (let i = 0; i < self._iccInfo.length; i++) { + self.iccProvider.unregisterIccMsg(self._iccInfo[i].clientId, + iccListener); + } + + self._iccInfo = null; + self._iccIds = null; + } + }; + + // _iccInfo is a local cache containing the information about the SIM cards + // that it is interesting for the Mobile ID flow. + // The index of this array does not necesarily need to match the real + // identifier of the SIM card ("clientId" or "serviceId" in RIL language). this._iccInfo = []; - for (let i = 0; i < gRil.numRadioInterfaces; i++) { - let rilContext = gRil.getRadioInterface(i).rilContext; + + for (let i = 0; i < this.ril.numRadioInterfaces; i++) { + let rilContext = this.ril.getRadioInterface(i).rilContext; if (!rilContext) { log.warn("Tried to get the RIL context for an invalid service ID " + i); continue; } + let info = rilContext.iccInfo; if (!info) { log.warn("No ICC info"); continue; } - let connection = mobileConnectionService.getItemByServiceId(i); + let connection = this.mobileConnectionService.getItemByServiceId(i); let voice = connection && connection.voice; let data = connection && connection.data; let operator = null; @@ -144,15 +198,21 @@ this.MobileIdentityManager = { } this._iccInfo.push({ + // Because it is possible that the _iccInfo array index doesn't match + // the real client ID, we need to store this value for later usage. + clientId: i, iccId: info.iccid, mcc: info.mcc, mnc: info.mnc, // GSM SIMs may have MSISDN while CDMA SIMs may have MDN msisdn: info.msisdn || info.mdn || null, operator: operator, - serviceId: i, roaming: voice && voice.roaming }); + + // We need to subscribe to ICC change notifications so we can refresh + // the cache if any change is observed. + this.iccProvider.registerIccMsg(i, iccListener); } return this._iccInfo; @@ -213,9 +273,9 @@ this.MobileIdentityManager = { log.debug("getVerificationOptionsForIcc " + aServiceId); log.debug("iccInfo ${}", this.iccInfo[aServiceId]); // First of all we need to check if we already have existing credentials - // for the given SIM information (ICC id or MSISDN). If we have no valid - // credentials, we have to check with the server which options to do we - // have to verify the associated phone number. + // for the given SIM information (ICC ID or MSISDN). If we have no valid + // credentials, we have to check with the server which options do we have + // to verify the associated phone number. return this.credStore.getByIccId(this.iccInfo[aServiceId].iccId) .then( (creds) => { @@ -566,8 +626,11 @@ this.MobileIdentityManager = { // This prompt will be considered as the permission prompt and its choice // will be remembered per origin by default. prompt: function prompt(aPrincipal, aManifestURL, aPhoneInfo) { - log.debug("prompt " + aPrincipal + ", " + aManifestURL + ", " + - aPhoneInfo); + log.debug("prompt ${principal} ${manifest} ${phoneInfo}", { + principal: aPrincipal, + manifest: aManifestURL, + phoneInfo: aPhoneInfo + }); let phoneInfoArray = []; @@ -578,19 +641,22 @@ this.MobileIdentityManager = { if (this.iccInfo) { for (let i = 0; i < this.iccInfo.length; i++) { // If we don't know the msisdn, there is no previous credentials and - // a silent verification is not possible, we don't allow the user to - // choose this option. - if (!this.iccInfo[i].msisdn && !this.iccInfo[i].credentials && - !this.iccInfo[i].canDoSilentVerification) { + // a silent verification is not possible or if the msisdn is the one + // that is already chosen, we don't list this SIM as an option. + if ((!this.iccInfo[i].msisdn && !this.iccInfo[i].credentials && + !this.iccInfo[i].canDoSilentVerification) || + ((aPhoneInfo) && + (this.iccInfo[i].msisdn == aPhoneInfo.msisdn || + this.iccInfo[i].iccId == aPhoneInfo.iccId))) { continue; } let phoneInfo = new MobileIdentityUIGluePhoneInfo( this.iccInfo[i].msisdn, this.iccInfo[i].operator, - i, // service ID - false, // external - false // primary + i, // service ID + this.iccInfo[i].iccId, // iccId + false // primary ); phoneInfoArray.push(phoneInfo); } @@ -674,7 +740,7 @@ this.MobileIdentityManager = { aCreds.msisdn, null, // operator undefined, // service ID - !!aCreds.iccId, // external + aCreds.iccId, // iccId true // primary ); } diff --git a/services/mobileid/MobileIdentityUIGlueCommon.jsm b/services/mobileid/MobileIdentityUIGlueCommon.jsm index 7f42cf44d79a..5b2956ee0fac 100644 --- a/services/mobileid/MobileIdentityUIGlueCommon.jsm +++ b/services/mobileid/MobileIdentityUIGlueCommon.jsm @@ -8,13 +8,14 @@ this.EXPORTED_SYMBOLS = ["MobileIdentityUIGluePhoneInfo", "MobileIdentityUIGluePromptResult"]; this.MobileIdentityUIGluePhoneInfo = function (aMsisdn, aOperator, aServiceId, - aExternal, aPrimary) { + aIccId, aPrimary) { this.msisdn = aMsisdn; this.operator = aOperator; this.serviceId = aServiceId; + this.iccId = aIccId; // A phone number is considered "external" when it doesn't or we don't know // if it does belong to any of the device SIM cards. - this.external = aExternal; + this.external = !!aIccId; this.primary = aPrimary; } diff --git a/services/mobileid/tests/xpcshell/head.js b/services/mobileid/tests/xpcshell/head.js index 9fb59c8279c3..751ae81c77eb 100644 --- a/services/mobileid/tests/xpcshell/head.js +++ b/services/mobileid/tests/xpcshell/head.js @@ -11,6 +11,9 @@ Cu.import("resource://gre/modules/Services.jsm"); (function initMobileIdTestingInfrastructure() { do_get_profile(); + const PREF_FORCE_HTTPS = "services.mobileid.forcehttps"; + Services.prefs.setBoolPref(PREF_FORCE_HTTPS, false); Services.prefs.setCharPref("services.mobileid.loglevel", "Debug"); - + Services.prefs.setCharPref("services.mobileid.server.uri", + "https://dummyurl.com"); }).call(this); diff --git a/services/mobileid/tests/xpcshell/test_mobileid_client.js b/services/mobileid/tests/xpcshell/test_mobileid_client.js index 9f6ad04ccce0..ba65bc4148f4 100644 --- a/services/mobileid/tests/xpcshell/test_mobileid_client.js +++ b/services/mobileid/tests/xpcshell/test_mobileid_client.js @@ -9,11 +9,6 @@ Cu.import("resource://gre/modules/MobileIdentityClient.jsm"); /* Setup */ -do_get_profile(); - -const PREF_FORCE_HTTPS = "services.mobileid.forcehttps"; -Services.prefs.setBoolPref(PREF_FORCE_HTTPS, false); - let client; let server = new HttpServer(); diff --git a/services/mobileid/tests/xpcshell/test_mobileid_manager.js b/services/mobileid/tests/xpcshell/test_mobileid_manager.js index 271b79de0cb7..aa2fdba7e37e 100644 --- a/services/mobileid/tests/xpcshell/test_mobileid_manager.js +++ b/services/mobileid/tests/xpcshell/test_mobileid_manager.js @@ -7,16 +7,6 @@ const Cm = Components.manager; Cu.import("resource://gre/modules/Promise.jsm"); Cu.import("resource://gre/modules/Services.jsm"); - -// We need to set the server url pref before importing MobileIdentityManager. -// Otherwise, it won't be able to import it as getting the non existing pref -// will throw. -Services.prefs.setCharPref("services.mobileid.server.uri", - "https://dummyurl.com"); - -// Set do_printging on. -Services.prefs.setCharPref("services.mobileid.loglevel", "do_print"); - Cu.import("resource://gre/modules/MobileIdentityManager.jsm"); Cu.import("resource://gre/modules/MobileIdentityCommon.jsm"); @@ -39,9 +29,58 @@ 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 MCC = 214; +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 CERTIFICATE = "eyJhbGciOiJEUzI1NiJ9.eyJsYXN0QXV0aEF0IjoxNDA0NDY5NzkyODc3LCJ2ZXJpZmllZE1TSVNETiI6IiszMTYxNzgxNTc1OCIsInB1YmxpYy1rZXkiOnsiYWxnb3JpdGhtIjoiRFMiLCJ5IjoiNGE5YzkzNDY3MWZhNzQ3YmM2ZjMyNjE0YTg1MzUyZjY5NDcwMDdhNTRkMDAxMDY4OWU5ZjJjZjc0ZGUwYTEwZTRlYjlmNDk1ZGFmZTA0NGVjZmVlNDlkN2YwOGU4ODQyMDJiOTE5OGRhNWZhZWE5MGUzZjRmNzE1YzZjNGY4Yjc3MGYxZTU4YWZhNDM0NzVhYmFiN2VlZGE1MmUyNjk2YzFmNTljNzMzYjFlYzBhNGNkOTM1YWIxYzkyNzAxYjNiYTA5ZDRhM2E2MzNjNTJmZjE2NGYxMWY3OTg1YzlmZjY3ZThmZDFlYzA2NDU3MTdkMjBiNDE4YmM5M2YzYzVkNCIsInAiOiJmZjYwMDQ4M2RiNmFiZmM1YjQ1ZWFiNzg1OTRiMzUzM2Q1NTBkOWYxYmYyYTk5MmE3YThkYWE2ZGMzNGY4MDQ1YWQ0ZTZlMGM0MjlkMzM0ZWVlYWFlZmQ3ZTIzZDQ4MTBiZTAwZTRjYzE0OTJjYmEzMjViYTgxZmYyZDVhNWIzMDVhOGQxN2ViM2JmNGEwNmEzNDlkMzkyZTAwZDMyOTc0NGE1MTc5MzgwMzQ0ZTgyYTE4YzQ3OTMzNDM4Zjg5MWUyMmFlZWY4MTJkNjljOGY3NWUzMjZjYjcwZWEwMDBjM2Y3NzZkZmRiZDYwNDYzOGMyZWY3MTdmYzI2ZDAyZTE3IiwicSI6ImUyMWUwNGY5MTFkMWVkNzk5MTAwOGVjYWFiM2JmNzc1OTg0MzA5YzMiLCJnIjoiYzUyYTRhMGZmM2I3ZTYxZmRmMTg2N2NlODQxMzgzNjlhNjE1NGY0YWZhOTI5NjZlM2M4MjdlMjVjZmE2Y2Y1MDhiOTBlNWRlNDE5ZTEzMzdlMDdhMmU5ZTJhM2NkNWRlYTcwNGQxNzVmOGViZjZhZjM5N2Q2OWUxMTBiOTZhZmIxN2M3YTAzMjU5MzI5ZTQ4MjliMGQwM2JiYzc4OTZiMTViNGFkZTUzZTEzMDg1OGNjMzRkOTYyNjlhYTg5MDQxZjQwOTEzNmM3MjQyYTM4ODk1YzlkNWJjY2FkNGYzODlhZjFkN2E0YmQxMzk4YmQwNzJkZmZhODk2MjMzMzk3YSJ9LCJwcmluY2lwYWwiOiIwMzgxOTgyYS0xZTgzLTI1NjYtNjgzZS05MDRmNDA0NGM1MGRAbXNpc2RuLWRldi5zdGFnZS5tb3phd3MubmV0IiwiaWF0IjoxNDA0NDY5NzgyODc3LCJleHAiOjE0MDQ0OTEzOTI4NzcsImlzcyI6Im1zaXNkbi1kZXYuc3RhZ2UubW96YXdzLm5ldCJ9." // === Helpers === @@ -363,7 +402,7 @@ function cleanup() { MobileIdentityManager.credStore = kMobileIdentityCredStore; MobileIdentityManager.client = kMobileIdentityClient; MobileIdentityManager.ui = null; - MobileIdentityManager.iccInfo = null; + MobileIdentityManager._iccInfo = []; removePermission(ORIGIN); } @@ -394,6 +433,7 @@ add_test(function() { MobileIdentityManager.credStore = credStore; let client = new MockClient(); MobileIdentityManager.client = client; + MobileIdentityManager._iccInfo = []; let promiseId = Date.now(); let mm = { @@ -898,7 +938,7 @@ add_test(function() { let client = new MockClient(); MobileIdentityManager.client = client; - MobileIdentityManager.iccInfo = []; + MobileIdentityManager._iccInfo = []; let promiseId = Date.now(); let mm = { @@ -990,7 +1030,7 @@ add_test(function() { }); MobileIdentityManager.client = client; - MobileIdentityManager.iccInfo = []; + MobileIdentityManager._iccInfo = []; let promiseId = Date.now(); let mm = { @@ -1317,3 +1357,109 @@ add_test(function() { } }); }); + +add_test(function() { + do_print("= ICC info change ="); + + do_register_cleanup(cleanup); + + do_test_pending(); + + let _sessionToken = Date.now(); + + MobileIdentityManager._iccInfo = null; + MobileIdentityManager._iccIds = null; + + MobileIdentityManager._ril = { + _interfaces: [RADIO_INTERFACE, ANOTHER_RADIO_INTERFACE], + get numRadioInterfaces() { + return this._interfaces.length; + }, + + getRadioInterface: function(aIndex) { + return this._interfaces[aIndex]; + } + }; + + MobileIdentityManager._mobileConnectionService = { + _interfaces: [RADIO_INTERFACE, ANOTHER_RADIO_INTERFACE], + getVoiceConnectionInfo: function(aIndex) { + return this._interfaces[aIndex].voice; + }, + + getDataConnectionInfo: function(aIndex) { + return this._interfaces[aIndex].data; + } + }; + + MobileIdentityManager._iccProvider = { + _listeners: [], + registerIccMsg: function(aClientId, aIccListener) { + this._listeners.push(aIccListener); + }, + unregisterIccMsg: function() { + this._listeners.pop(); + } + }; + + let ui = new MockUi(); + ui.startFlow = function() { + // At this point we've already built the ICC cache. + let interfaces = MobileIdentityManager._ril._interfaces; + for (let i = 0; i < interfaces.length; i++) { + let interfaceIccInfo = interfaces[i].rilContext.iccInfo; + let mIdIccInfo = MobileIdentityManager._iccInfo[i]; + do_check_eq(interfaceIccInfo.iccid, mIdIccInfo.iccId); + do_check_eq(interfaceIccInfo.mcc, mIdIccInfo.mcc); + do_check_eq(interfaceIccInfo.mnc, mIdIccInfo.mnc); + do_check_eq(interfaceIccInfo.msisdn, mIdIccInfo.msisdn); + do_check_eq(interfaceIccInfo.operator, mIdIccInfo.operator); + } + + // We should have listeners for each valid icc. + do_check_eq(MobileIdentityManager._iccProvider._listeners.length, 2); + + // We can mock an ICC change event at this point. + MobileIdentityManager._iccProvider._listeners[0].notifyIccInfoChanged(); + + // After the ICC change event the caches should be null. + do_check_null(MobileIdentityManager._iccInfo); + do_check_null(MobileIdentityManager._iccIds); + + // And we should have unregistered all listeners for ICC change events. + do_check_eq(MobileIdentityManager._iccProvider._listeners.length, 0); + + do_test_finished(); + run_next_test(); + }; + MobileIdentityManager.ui = ui; + + let credStore = new MockCredStore(); + credStore.getByOrigin = function() { + // Initially the ICC caches should be null. + do_check_null(MobileIdentityManager._iccInfo); + do_check_null(MobileIdentityManager._iccIds); + return Promise.resolve(null); + }; + MobileIdentityManager.credStore = credStore; + + let client = new MockClient(); + MobileIdentityManager.client = client; + + let promiseId = Date.now(); + let mm = { + sendAsyncMessage: function() {} + }; + + addPermission(Ci.nsIPermissionManager.ALLOW_ACTION); + + MobileIdentityManager.receiveMessage({ + name: GET_ASSERTION_IPC_MSG, + principal: PRINCIPAL, + target: mm, + json: { + promiseId: promiseId, + options: {} + } + }); +});