test_loginmgr_storage.js (7809B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 // Tests for FxAccounts, storage and the master password. 7 8 // See verbose logging from FxAccounts.sys.mjs 9 Services.prefs.setStringPref("identity.fxaccounts.loglevel", "Trace"); 10 11 const { FxAccounts } = ChromeUtils.importESModule( 12 "resource://gre/modules/FxAccounts.sys.mjs" 13 ); 14 const { FXA_PWDMGR_HOST, FXA_PWDMGR_REALM } = ChromeUtils.importESModule( 15 "resource://gre/modules/FxAccountsCommon.sys.mjs" 16 ); 17 18 // Use the system global to get at our LoginManagerStorage object, so we can 19 // mock the prototype. 20 var { LoginManagerStorage } = ChromeUtils.importESModule( 21 "resource://gre/modules/FxAccountsStorage.sys.mjs" 22 ); 23 var isLoggedIn = true; 24 LoginManagerStorage.prototype.__defineGetter__("_isLoggedIn", () => isLoggedIn); 25 26 function setLoginMgrLoggedInState(loggedIn) { 27 isLoggedIn = loggedIn; 28 } 29 30 initTestLogging("Trace"); 31 32 async function getLoginMgrData() { 33 let logins = await Services.logins.searchLoginsAsync({ 34 origin: FXA_PWDMGR_HOST, 35 httpRealm: FXA_PWDMGR_REALM, 36 }); 37 if (!logins.length) { 38 return null; 39 } 40 Assert.equal(logins.length, 1, "only 1 login available"); 41 return logins[0]; 42 } 43 44 function createFxAccounts() { 45 return new FxAccounts({ 46 _fxAccountsClient: { 47 async registerDevice() { 48 return { id: "deviceAAAAAA" }; 49 }, 50 async recoveryEmailStatus() { 51 return { verified: true }; 52 }, 53 async signOut() {}, 54 }, 55 updateDeviceRegistration() {}, 56 _getDeviceName() { 57 return "mock device name"; 58 }, 59 observerPreloads: [], 60 fxaPushService: { 61 async registerPushEndpoint() { 62 return { 63 endpoint: "http://mochi.test:8888", 64 getKey() { 65 return null; 66 }, 67 }; 68 }, 69 async unsubscribe() { 70 return true; 71 }, 72 }, 73 }); 74 } 75 76 add_task(async function test_simple() { 77 let fxa = createFxAccounts(); 78 79 let creds = { 80 uid: "abcd", 81 email: "test@example.com", 82 sessionToken: "sessionToken", 83 scopedKeys: { 84 ...MOCK_ACCOUNT_KEYS.scopedKeys, 85 }, 86 verified: true, 87 }; 88 await fxa._internal.setSignedInUser(creds); 89 90 // This should have stored stuff in both the .json file in the profile 91 // dir, and the login dir. 92 let path = PathUtils.join(PathUtils.profileDir, "signedInUser.json"); 93 let data = await IOUtils.readJSON(path); 94 95 Assert.strictEqual( 96 data.accountData.email, 97 creds.email, 98 "correct email in the clear text" 99 ); 100 Assert.strictEqual( 101 data.accountData.sessionToken, 102 creds.sessionToken, 103 "correct sessionToken in the clear text" 104 ); 105 Assert.strictEqual( 106 data.accountData.verified, 107 creds.verified, 108 "correct verified flag" 109 ); 110 111 Assert.ok( 112 !("scopedKeys" in data.accountData), 113 "scopedKeys not stored in clear text" 114 ); 115 116 let login = await getLoginMgrData(); 117 Assert.strictEqual(login.username, creds.uid, "uid used for username"); 118 let loginData = JSON.parse(login.password); 119 Assert.strictEqual( 120 loginData.version, 121 data.version, 122 "same version flag in both places" 123 ); 124 Assert.deepEqual( 125 loginData.accountData.scopedKeys, 126 creds.scopedKeys, 127 "correct scoped keys in the login mgr" 128 ); 129 Assert.ok(!("email" in loginData), "email not stored in the login mgr json"); 130 Assert.ok( 131 !("sessionToken" in loginData), 132 "sessionToken not stored in the login mgr json" 133 ); 134 Assert.ok( 135 !("verified" in loginData), 136 "verified not stored in the login mgr json" 137 ); 138 139 await fxa.signOut(/* localOnly = */ true); 140 Assert.strictEqual( 141 await getLoginMgrData(), 142 null, 143 "login mgr data deleted on logout" 144 ); 145 }); 146 147 add_task(async function test_MPLocked() { 148 let fxa = createFxAccounts(); 149 150 let creds = { 151 uid: "abcd", 152 email: "test@example.com", 153 sessionToken: "sessionToken", 154 scopedKeys: { 155 ...MOCK_ACCOUNT_KEYS.scopedKeys, 156 }, 157 verified: true, 158 }; 159 160 Assert.strictEqual( 161 await getLoginMgrData(), 162 null, 163 "no login mgr at the start" 164 ); 165 // tell the storage that the MP is locked. 166 setLoginMgrLoggedInState(false); 167 await fxa._internal.setSignedInUser(creds); 168 169 // This should have stored stuff in the .json, and the login manager stuff 170 // will not exist. 171 let path = PathUtils.join(PathUtils.profileDir, "signedInUser.json"); 172 let data = await IOUtils.readJSON(path); 173 174 Assert.strictEqual( 175 data.accountData.email, 176 creds.email, 177 "correct email in the clear text" 178 ); 179 Assert.strictEqual( 180 data.accountData.sessionToken, 181 creds.sessionToken, 182 "correct sessionToken in the clear text" 183 ); 184 Assert.strictEqual( 185 data.accountData.verified, 186 creds.verified, 187 "correct verified flag" 188 ); 189 190 Assert.ok( 191 !("scopedKeys" in data.accountData), 192 "scopedKeys not stored in clear text" 193 ); 194 195 Assert.strictEqual( 196 await getLoginMgrData(), 197 null, 198 "login mgr data doesn't exist" 199 ); 200 await fxa.signOut(/* localOnly = */ true); 201 }); 202 203 add_task(async function test_consistentWithMPEdgeCases() { 204 setLoginMgrLoggedInState(true); 205 206 let fxa = createFxAccounts(); 207 208 let creds1 = { 209 uid: "uid1", 210 email: "test@example.com", 211 sessionToken: "sessionToken", 212 scopedKeys: { 213 [SCOPE_OLD_SYNC]: { 214 kid: "key id 1", 215 k: "key material 1", 216 kty: "oct", 217 }, 218 }, 219 verified: true, 220 }; 221 222 let creds2 = { 223 uid: "uid2", 224 email: "test2@example.com", 225 sessionToken: "sessionToken2", 226 [SCOPE_OLD_SYNC]: { 227 kid: "key id 2", 228 k: "key material 2", 229 kty: "oct", 230 }, 231 verified: false, 232 }; 233 234 // Log a user in while MP is unlocked. 235 await fxa._internal.setSignedInUser(creds1); 236 237 // tell the storage that the MP is locked - this will prevent logout from 238 // being able to clear the data. 239 setLoginMgrLoggedInState(false); 240 241 // now set the second credentials. 242 await fxa._internal.setSignedInUser(creds2); 243 244 // We should still have creds1 data in the login manager. 245 let login = await getLoginMgrData(); 246 Assert.strictEqual(login.username, creds1.uid); 247 // and that we do have the first scopedKeys in the login manager. 248 Assert.deepEqual( 249 JSON.parse(login.password).accountData.scopedKeys, 250 creds1.scopedKeys, 251 "stale data still in login mgr" 252 ); 253 254 // Make a new FxA instance (otherwise the values in memory will be used) 255 // and we want the login manager to be unlocked. 256 setLoginMgrLoggedInState(true); 257 fxa = createFxAccounts(); 258 259 let accountData = await fxa.getSignedInUser(); 260 Assert.strictEqual(accountData.email, creds2.email); 261 // we should have no scopedKeys at all. 262 Assert.strictEqual( 263 accountData.scopedKeys, 264 undefined, 265 "stale scopedKey wasn't used" 266 ); 267 await fxa.signOut(/* localOnly = */ true); 268 }); 269 270 // A test for the fact we will accept either a UID or email when looking in 271 // the login manager. 272 add_task(async function test_uidMigration() { 273 setLoginMgrLoggedInState(true); 274 Assert.strictEqual( 275 await getLoginMgrData(), 276 null, 277 "expect no logins at the start" 278 ); 279 280 // create the login entry using email as a key. 281 let contents = { 282 scopedKeys: { 283 ...MOCK_ACCOUNT_KEYS.scopedKeys, 284 }, 285 }; 286 287 let loginInfo = new Components.Constructor( 288 "@mozilla.org/login-manager/loginInfo;1", 289 Ci.nsILoginInfo, 290 "init" 291 ); 292 let login = new loginInfo( 293 FXA_PWDMGR_HOST, 294 null, // aFormActionOrigin, 295 FXA_PWDMGR_REALM, // aHttpRealm, 296 "foo@bar.com", // aUsername 297 JSON.stringify(contents), // aPassword 298 "", // aUsernameField 299 "" 300 ); // aPasswordField 301 await Services.logins.addLoginAsync(login); 302 303 // ensure we read it. 304 let storage = new LoginManagerStorage(); 305 let got = await storage.get("uid", "foo@bar.com"); 306 Assert.deepEqual(got, contents); 307 });