test_password_store.js (9014B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 const { LoginRec } = ChromeUtils.importESModule( 5 "resource://services-sync/engines/passwords.sys.mjs" 6 ); 7 const { Service } = ChromeUtils.importESModule( 8 "resource://services-sync/service.sys.mjs" 9 ); 10 const { SyncedRecordsTelemetry } = ChromeUtils.importESModule( 11 "resource://services-sync/telemetry.sys.mjs" 12 ); 13 14 async function checkRecord( 15 name, 16 record, 17 expectedCount, 18 timeCreated, 19 expectedTimeCreated, 20 timePasswordChanged, 21 expectedTimePasswordChanged, 22 recordIsUpdated 23 ) { 24 let engine = Service.engineManager.get("passwords"); 25 let store = engine._store; 26 27 let logins = await Services.logins.searchLoginsAsync({ 28 origin: record.hostname, 29 formActionOrigin: record.formSubmitURL, 30 }); 31 32 _("Record" + name + ":" + JSON.stringify(logins)); 33 _("Count" + name + ":" + logins.length); 34 35 Assert.equal(logins.length, expectedCount); 36 37 if (expectedCount > 0) { 38 Assert.ok(!!(await store.getAllIDs())[record.id]); 39 let stored_record = logins[0].QueryInterface(Ci.nsILoginMetaInfo); 40 41 if (timeCreated !== undefined) { 42 Assert.equal(stored_record.timeCreated, expectedTimeCreated); 43 } 44 45 if (timePasswordChanged !== undefined) { 46 if (recordIsUpdated) { 47 Assert.greaterOrEqual( 48 stored_record.timePasswordChanged, 49 expectedTimePasswordChanged 50 ); 51 } else { 52 Assert.equal( 53 stored_record.timePasswordChanged, 54 expectedTimePasswordChanged 55 ); 56 } 57 return stored_record.timePasswordChanged; 58 } 59 } else { 60 Assert.ok(!(await store.getAllIDs())[record.id]); 61 } 62 return undefined; 63 } 64 65 async function changePassword( 66 name, 67 hostname, 68 password, 69 expectedCount, 70 timeCreated, 71 expectedTimeCreated, 72 timePasswordChanged, 73 expectedTimePasswordChanged, 74 insert, 75 recordIsUpdated 76 ) { 77 const BOGUS_GUID = "zzzzzz" + hostname; 78 let record = new LoginRec("passwords", BOGUS_GUID); 79 record.cleartext = { 80 id: BOGUS_GUID, 81 hostname, 82 formSubmitURL: hostname, 83 username: "john", 84 password, 85 usernameField: "username", 86 passwordField: "password", 87 }; 88 89 if (timeCreated !== undefined) { 90 record.timeCreated = timeCreated; 91 } 92 93 if (timePasswordChanged !== undefined) { 94 record.timePasswordChanged = timePasswordChanged; 95 } 96 97 let engine = Service.engineManager.get("passwords"); 98 let store = engine._store; 99 100 if (insert) { 101 let countTelemetry = new SyncedRecordsTelemetry(); 102 Assert.equal( 103 (await store.applyIncomingBatch([record], countTelemetry)).length, 104 0 105 ); 106 } 107 108 return checkRecord( 109 name, 110 record, 111 expectedCount, 112 timeCreated, 113 expectedTimeCreated, 114 timePasswordChanged, 115 expectedTimePasswordChanged, 116 recordIsUpdated 117 ); 118 } 119 120 async function test_apply_records_with_times( 121 hostname, 122 timeCreated, 123 timePasswordChanged 124 ) { 125 // The following record is going to be inserted in the store and it needs 126 // to be found there. Then its timestamps are going to be compared to 127 // the expected values. 128 await changePassword( 129 " ", 130 hostname, 131 "password", 132 1, 133 timeCreated, 134 timeCreated, 135 timePasswordChanged, 136 timePasswordChanged, 137 true 138 ); 139 } 140 141 async function test_apply_multiple_records_with_times() { 142 // The following records are going to be inserted in the store and they need 143 // to be found there. Then their timestamps are going to be compared to 144 // the expected values. 145 await changePassword( 146 "A", 147 "http://foo.a.com", 148 "password", 149 1, 150 undefined, 151 undefined, 152 undefined, 153 undefined, 154 true 155 ); 156 await changePassword( 157 "B", 158 "http://foo.b.com", 159 "password", 160 1, 161 1000, 162 1000, 163 undefined, 164 undefined, 165 true 166 ); 167 await changePassword( 168 "C", 169 "http://foo.c.com", 170 "password", 171 1, 172 undefined, 173 undefined, 174 1000, 175 1000, 176 true 177 ); 178 await changePassword( 179 "D", 180 "http://foo.d.com", 181 "password", 182 1, 183 1000, 184 1000, 185 1000, 186 1000, 187 true 188 ); 189 190 // The following records are not going to be inserted in the store and they 191 // are not going to be found there. 192 await changePassword( 193 "NotInStoreA", 194 "http://foo.aaaa.com", 195 "password", 196 0, 197 undefined, 198 undefined, 199 undefined, 200 undefined, 201 false 202 ); 203 await changePassword( 204 "NotInStoreB", 205 "http://foo.bbbb.com", 206 "password", 207 0, 208 1000, 209 1000, 210 undefined, 211 undefined, 212 false 213 ); 214 await changePassword( 215 "NotInStoreC", 216 "http://foo.cccc.com", 217 "password", 218 0, 219 undefined, 220 undefined, 221 1000, 222 1000, 223 false 224 ); 225 await changePassword( 226 "NotInStoreD", 227 "http://foo.dddd.com", 228 "password", 229 0, 230 1000, 231 1000, 232 1000, 233 1000, 234 false 235 ); 236 } 237 238 async function test_apply_same_record_with_different_times() { 239 // The following record is going to be inserted multiple times in the store 240 // and it needs to be found there. Then its timestamps are going to be 241 // compared to the expected values. 242 243 /* eslint-disable no-unused-vars */ 244 /* The eslint linter thinks that timePasswordChanged is unused, even though 245 it is passed as an argument to changePassword. */ 246 var timePasswordChanged = 100; 247 timePasswordChanged = await changePassword( 248 "A", 249 "http://a.tn", 250 "password", 251 1, 252 100, 253 100, 254 100, 255 timePasswordChanged, 256 true 257 ); 258 timePasswordChanged = await changePassword( 259 "A", 260 "http://a.tn", 261 "password", 262 1, 263 100, 264 100, 265 800, 266 timePasswordChanged, 267 true, 268 true 269 ); 270 timePasswordChanged = await changePassword( 271 "A", 272 "http://a.tn", 273 "password", 274 1, 275 500, 276 100, 277 800, 278 timePasswordChanged, 279 true, 280 true 281 ); 282 timePasswordChanged = await changePassword( 283 "A", 284 "http://a.tn", 285 "password2", 286 1, 287 500, 288 100, 289 1536213005222, 290 timePasswordChanged, 291 true, 292 true 293 ); 294 timePasswordChanged = await changePassword( 295 "A", 296 "http://a.tn", 297 "password2", 298 1, 299 500, 300 100, 301 800, 302 timePasswordChanged, 303 true, 304 true 305 ); 306 /* eslint-enable no-unused-vars */ 307 } 308 309 async function test_LoginRec_toString(store, recordData) { 310 let rec = await store.createRecord(recordData.id); 311 ok(rec); 312 ok(!rec.toString().includes(rec.password)); 313 } 314 315 add_task(async function run_test() { 316 const BOGUS_GUID_A = "zzzzzzzzzzzz"; 317 const BOGUS_GUID_B = "yyyyyyyyyyyy"; 318 let recordA = new LoginRec("passwords", BOGUS_GUID_A); 319 let recordB = new LoginRec("passwords", BOGUS_GUID_B); 320 recordA.cleartext = { 321 id: BOGUS_GUID_A, 322 hostname: "http://foo.bar.com", 323 formSubmitURL: "http://foo.bar.com", 324 httpRealm: "secure", 325 username: "john", 326 password: "smith", 327 usernameField: "username", 328 passwordField: "password", 329 }; 330 recordB.cleartext = { 331 id: BOGUS_GUID_B, 332 hostname: "http://foo.baz.com", 333 formSubmitURL: "http://foo.baz.com", 334 username: "john", 335 password: "smith", 336 usernameField: "username", 337 passwordField: "password", 338 unknownStr: "an unknown string from another field", 339 }; 340 341 let engine = Service.engineManager.get("passwords"); 342 let store = engine._store; 343 344 try { 345 let countTelemetry = new SyncedRecordsTelemetry(); 346 Assert.equal( 347 (await store.applyIncomingBatch([recordA, recordB], countTelemetry)) 348 .length, 349 0 350 ); 351 352 // Only the good record makes it to Services.logins. 353 let badLogins = await Services.logins.searchLoginsAsync({ 354 origin: recordA.hostname, 355 formActionOrigin: recordA.formSubmitURL, 356 httpRealm: recordA.httpRealm, 357 }); 358 let goodLogins = await Services.logins.searchLoginsAsync({ 359 origin: recordB.hostname, 360 formActionOrigin: recordB.formSubmitURL, 361 }); 362 363 _("Bad: " + JSON.stringify(badLogins)); 364 _("Good: " + JSON.stringify(goodLogins)); 365 _("Count: " + badLogins.length + ", " + goodLogins.length); 366 367 Assert.equal(goodLogins.length, 1); 368 Assert.equal(badLogins.length, 0); 369 370 // applyIncoming should've put any unknown fields from the server 371 // into a catch-all unknownFields field 372 Assert.equal( 373 goodLogins[0].unknownFields, 374 JSON.stringify({ 375 unknownStr: "an unknown string from another field", 376 }) 377 ); 378 379 Assert.ok(!!(await store.getAllIDs())[BOGUS_GUID_B]); 380 Assert.ok(!(await store.getAllIDs())[BOGUS_GUID_A]); 381 382 await test_LoginRec_toString(store, recordB); 383 384 await test_apply_records_with_times( 385 "http://afoo.baz.com", 386 undefined, 387 undefined 388 ); 389 await test_apply_records_with_times("http://bfoo.baz.com", 1000, undefined); 390 await test_apply_records_with_times("http://cfoo.baz.com", undefined, 2000); 391 await test_apply_records_with_times("http://dfoo.baz.com", 1000, 2000); 392 393 await test_apply_multiple_records_with_times(); 394 395 await test_apply_same_record_with_different_times(); 396 } finally { 397 await store.wipe(); 398 } 399 });