test_backup.py (34647B)
1 # This Source Code Form is subject to the terms of the Mozilla Public 2 # License, v. 2.0. If a copy of the MPL was not distributed with this 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5 import os 6 import shutil 7 import tempfile 8 9 import mozfile 10 from marionette_harness import MarionetteTestCase 11 12 13 class BackupTest(MarionetteTestCase): 14 # This is the DB key that will be computed for the http2-ca.pem certificate 15 # that's included in a support-file for this test. 16 _cert_db_key = "AAAAAAAAAAAAAAAUAAAAG0Wbze8lahTcE4RhwEqMtTpThrzjMBkxFzAVBgNVBAMMDiBIVFRQMiBUZXN0IENB" 17 18 def setUp(self): 19 MarionetteTestCase.setUp(self) 20 21 # We need to force the service to be enabled because it's disabled 22 # by default for Marionette. Also "browser.backup.log" has to be set 23 # to true before Firefox starts in order for it to be displayed. 24 self.marionette.enforce_gecko_prefs({ 25 "browser.backup.enabled": True, 26 "browser.backup.log": True, 27 "browser.backup.archive.enabled": True, 28 "browser.backup.restore.enabled": True, 29 "browser.backup.archive.overridePlatformCheck": True, 30 "browser.backup.restore.overridePlatformCheck": True, 31 # Necessary to test Session Restore from backup, which relies on 32 # the crash restore mechanism. 33 "browser.sessionstore.resume_from_crash": True, 34 }) 35 36 self.marionette.set_context("chrome") 37 38 def tearDown(self): 39 # Restart Firefox with a new profile to get rid from all modifications. 40 self.marionette.quit() 41 self.marionette.instance.switch_profile() 42 self.marionette.start_session() 43 44 MarionetteTestCase.tearDown(self) 45 46 def test_backup(self): 47 self.add_test_cookie() 48 self.add_test_login() 49 self.add_test_certificate() 50 self.add_test_saved_address() 51 self.add_test_identity_credential() 52 self.add_test_form_history() 53 self.add_test_asrouter_snippets_data() 54 self.add_test_protections_data() 55 self.add_test_bookmarks() 56 self.add_test_history() 57 self.add_test_preferences() 58 self.add_test_permissions() 59 60 # We want to make sure that any payment methods in this testing profile 61 # are properly encrypted using OSKeyStore, and that the encrypted 62 # backup will properly extract and recover from the original OSKeyStore 63 # secret. 64 # 65 # What we _don't_ want to do is encrypt or extract the OSKeyStore secret 66 # used by this machine's _actual_ Firefox instance, if one exists 67 # (since they're all shared). We also definitely do not want to 68 # accidentally overwrite that secret. 69 # 70 # We solve this by poking a new STORE_LABEL value into the OSKeyStore 71 # module before we do the following: 72 # 73 # 1. Store payment methods 74 # 2. Enable encryption 75 # 76 # Once that'd one, we delete the temporary OSKeyStore row that we 77 # created. This technique is similar to the one used in 78 # OSKeyStoreTestUtils, which is unfortunately not a module that is 79 # available to Marionette tests. 80 backupOSKeyStoreLabel = self.marionette.execute_script( 81 """ 82 const { OSKeyStore } = ChromeUtils.importESModule( 83 "resource://gre/modules/OSKeyStore.sys.mjs" 84 ); 85 86 const BACKUP_OSKEYSTORE_LABEL = "test-" + Math.random().toString(36).substr(2); 87 OSKeyStore.STORE_LABEL = BACKUP_OSKEYSTORE_LABEL; 88 return BACKUP_OSKEYSTORE_LABEL; 89 """ 90 ) 91 92 # Now that we've got the fake OSKeyStore set up, we can insert our 93 # testing payment methods. 94 self.add_test_payment_methods() 95 96 # Restart the browser to force all of the test data we just added 97 # to be flushed to disk and to be made ready for backup 98 self.marionette.restart() 99 100 # We want to validate that TabState is flushed before serializing the 101 # backup, so run this test in the same browser instance we invoke the 102 # backup in. 103 self.add_test_sessionstore() 104 105 # Put the OSKeyStore label back, since it would have been cleared 106 # from memory during the restart. 107 self.marionette.execute_script( 108 """ 109 const { OSKeyStore } = ChromeUtils.importESModule( 110 "resource://gre/modules/OSKeyStore.sys.mjs" 111 ); 112 113 const BACKUP_OSKEYSTORE_LABEL = arguments[0]; 114 OSKeyStore.STORE_LABEL = BACKUP_OSKEYSTORE_LABEL; 115 """, 116 script_args=[backupOSKeyStoreLabel], 117 ) 118 119 archiveDestPath = os.path.join(tempfile.gettempdir(), "backup-dest") 120 recoveryCode = "This is a test password" 121 archivePath = self.marionette.execute_async_script( 122 """ 123 124 const { BackupService } = ChromeUtils.importESModule("resource:///modules/backup/BackupService.sys.mjs"); 125 let bs = BackupService.init(); 126 if (!bs) { 127 throw new Error("Could not get initialized BackupService."); 128 } 129 130 let [archiveDestPath, recoveryCode, outerResolve] = arguments; 131 bs.setParentDirPath(archiveDestPath); 132 133 (async () => { 134 135 await bs.enableEncryption(recoveryCode); 136 137 let { archivePath } = await bs.createBackup(); 138 if (!archivePath) { 139 throw new Error("Could not create backup."); 140 } 141 return archivePath; 142 })().then(outerResolve); 143 """, 144 script_args=[archiveDestPath, recoveryCode], 145 ) 146 147 # Now we clean up our temporary OSKeyStore from the OS's secure storage. 148 # We won't need it anymore. 149 self.marionette.execute_async_script( 150 """ 151 const { OSKeyStore } = ChromeUtils.importESModule( 152 "resource://gre/modules/OSKeyStore.sys.mjs" 153 ); 154 155 let [outerResolve] = arguments; 156 (async () => { 157 await OSKeyStore.cleanup(); 158 })().then(outerResolve); 159 """ 160 ) 161 162 recoveryPath = os.path.join(tempfile.gettempdir(), "recovery") 163 shutil.rmtree(recoveryPath, ignore_errors=True) 164 165 # Start a brand new profile, one without any of the data we created or 166 # backed up. This is the one that we'll be starting recovery from. 167 self.marionette.quit() 168 self.marionette.instance.switch_profile() 169 self.marionette.start_session() 170 self.marionette.set_context("chrome") 171 172 # Recover the created backup into a new profile directory. Also get out 173 # the client ID of this profile, because we're going to want to make 174 # sure that this client ID is not inherited from the intermediate profile. 175 [ 176 newProfileName, 177 newProfilePath, 178 intermediateClientID, 179 osKeyStoreLabel, 180 ] = self.marionette.execute_async_script( 181 """ 182 const { OSKeyStore } = ChromeUtils.importESModule("resource://gre/modules/OSKeyStore.sys.mjs"); 183 const { ClientID } = ChromeUtils.importESModule("resource://gre/modules/ClientID.sys.mjs"); 184 const { BackupService } = ChromeUtils.importESModule("resource:///modules/backup/BackupService.sys.mjs"); 185 let bs = BackupService.get(); 186 if (!bs) { 187 throw new Error("Could not get initialized BackupService."); 188 } 189 190 let [archivePath, recoveryCode, recoveryPath, outerResolve] = arguments; 191 (async () => { 192 let newProfileRootPath = await IOUtils.createUniqueDirectory( 193 PathUtils.tempDir, 194 "recoverFromBackupArchiveTest-newProfileRoot" 195 ); 196 197 // This is some hackery to make it so that OSKeyStore doesn't kick 198 // off an OS authentication dialog in our test, and also to make 199 // sure we don't blow away the _real_ OSKeyStore key for the browser 200 // on the system that this test is running on. Normally, I'd use 201 // OSKeyStoreTestUtils.setup to do this, but apparently the 202 // testing-common modules aren't available in Marionette tests. 203 const ORIGINAL_STORE_LABEL = OSKeyStore.STORE_LABEL; 204 OSKeyStore.STORE_LABEL = "test-" + Math.random().toString(36).substr(2); 205 206 let newProfile = await bs.recoverFromBackupArchive(archivePath, recoveryCode, false, recoveryPath, newProfileRootPath); 207 208 if (!newProfile) { 209 throw new Error("Could not create recovery profile."); 210 } 211 212 let intermediateClientID = await ClientID.getClientID(); 213 214 return [newProfile.name, newProfile.rootDir.path, intermediateClientID, OSKeyStore.STORE_LABEL]; 215 })().then(outerResolve); 216 """, 217 script_args=[archivePath, recoveryCode, recoveryPath], 218 ) 219 220 print(f"Recovery name: {newProfileName}") 221 print(f"Recovery path: {newProfilePath}") 222 print(f"Intermediate clientID: {intermediateClientID}") 223 print(f"Persisting fake OSKeyStore label: {osKeyStoreLabel}") 224 225 self.marionette.quit() 226 originalProfile = self.marionette.instance.profile 227 self.marionette.instance.profile = newProfilePath 228 self.marionette.start_session() 229 self.marionette.set_context("chrome") 230 231 # Ensure that all postRecovery actions have completed, and that 232 # encryption is enabled. 233 encryptionEnabled = self.marionette.execute_async_script( 234 """ 235 const { BackupService } = ChromeUtils.importESModule("resource:///modules/backup/BackupService.sys.mjs"); 236 let bs = BackupService.get(); 237 if (!bs) { 238 throw new Error("Could not get initialized BackupService."); 239 } 240 241 let [outerResolve] = arguments; 242 (async () => { 243 await bs.postRecoveryComplete; 244 245 await bs.loadEncryptionState(); 246 return bs.state.encryptionEnabled; 247 })().then(outerResolve); 248 """ 249 ) 250 self.assertTrue(encryptionEnabled) 251 252 self.verify_recovered_test_cookie() 253 self.verify_recovered_test_login() 254 self.verify_recovered_test_certificate() 255 self.verify_recovered_saved_address() 256 self.verify_recovered_identity_credential() 257 self.verify_recovered_form_history() 258 self.verify_recovered_asrouter_snippets_data() 259 self.verify_recovered_protections_data() 260 self.verify_recovered_bookmarks() 261 self.verify_recovered_history() 262 self.verify_recovered_preferences() 263 self.verify_recovered_permissions() 264 self.verify_recovered_payment_methods(osKeyStoreLabel) 265 self.verify_recovered_sessionstore() 266 267 # Clean up the temporary OSKeyStore label 268 self.marionette.execute_async_script( 269 """ 270 const { OSKeyStore } = ChromeUtils.importESModule("resource://gre/modules/OSKeyStore.sys.mjs"); 271 let [osKeyStoreLabel, outerResolve] = arguments; 272 273 OSKeyStore.STORE_LABEL = osKeyStoreLabel; 274 275 (async () => { 276 await OSKeyStore.cleanup(); 277 })().then(outerResolve); 278 """, 279 script_args=[osKeyStoreLabel], 280 ) 281 282 # Now also ensure that the recovered profile new client ID and not that 283 # one from the intermediate profile that initiated recovery. 284 recoveredClientID = self.marionette.execute_async_script( 285 """ 286 const { ClientID } = ChromeUtils.importESModule("resource://gre/modules/ClientID.sys.mjs"); 287 let [outerResolve] = arguments; 288 (async () => { 289 return ClientID.getClientID(); 290 })().then(outerResolve); 291 """ 292 ) 293 self.assertNotEqual(recoveredClientID, intermediateClientID) 294 295 self.marionette.quit() 296 self.marionette.instance.profile = originalProfile 297 self.marionette.start_session() 298 self.marionette.set_context("chrome") 299 300 # Don't pollute the profile list by getting rid of the one we just created. 301 self.marionette.execute_async_script( 302 """ 303 let [newProfileName, outerResolve] = arguments; 304 let profileSvc = Cc["@mozilla.org/toolkit/profile-service;1"].getService( 305 Ci.nsIToolkitProfileService 306 ); 307 let profile = profileSvc.getProfileByName(newProfileName); 308 profile.remove(true); 309 profileSvc.asyncFlush().then(outerResolve); 310 """, 311 script_args=[newProfileName], 312 ) 313 314 # Cleanup the archive we moved, and the recovery folder we decompressed to. 315 mozfile.remove(archivePath) 316 mozfile.remove(recoveryPath) 317 318 def test_backup_disablement_in_new_session(self): 319 archiveDestPath = os.path.join( 320 tempfile.gettempdir(), "backup-dest-disable-test" 321 ) 322 323 [archivePath, lastBackupFileName] = self.marionette.execute_async_script( 324 """ 325 const { BackupService } = ChromeUtils.importESModule("resource:///modules/backup/BackupService.sys.mjs"); 326 let bs = BackupService.init(); 327 if (!bs) { 328 throw new Error("Could not get initialized BackupService."); 329 } 330 331 let [archiveDestPath, outerResolve] = arguments; 332 bs.setParentDirPath(archiveDestPath); 333 334 (async () => { 335 bs.setScheduledBackups(true); 336 let { archivePath } = await bs.createBackup(); 337 if (!archivePath) { 338 throw new Error("Could not create backup."); 339 } 340 341 let lastBackupFileName = Services.prefs.getStringPref("browser.backup.scheduled.last-backup-file", ""); 342 return [archivePath, lastBackupFileName]; 343 })().then(outerResolve); 344 """, 345 script_args=[archiveDestPath], 346 ) 347 348 print(f"Created backup at: {archivePath}") 349 print(f"Last backup filename: {lastBackupFileName}") 350 351 self.marionette.quit() 352 self.marionette.start_session() 353 self.marionette.set_context("chrome") 354 355 if os.path.exists(archivePath): 356 print(f"File size: {os.path.getsize(archivePath)} bytes") 357 358 self.marionette.execute_async_script( 359 """ 360 361 ChromeUtils.defineESModuleGetters(this, { 362 BackupService: "resource:///modules/backup/BackupService.sys.mjs", 363 ASRouterTargeting: "resource:///modules/asrouter/ASRouterTargeting.sys.mjs", 364 }); 365 366 let bs = BackupService.init(); 367 if (!bs) { 368 throw new Error("Could not get initialized BackupService."); 369 } 370 371 let [outerResolve] = arguments; 372 (async () => { 373 await ASRouterTargeting.Environment.backupsInfo; 374 await bs.cleanupBackupFiles(); 375 bs.setScheduledBackups(false); 376 })().then(outerResolve); 377 """, 378 script_args=[], 379 ) 380 381 archiveDeletedAfterDisable = not os.path.exists(archivePath) 382 self.assertTrue( 383 archiveDeletedAfterDisable, 384 f"Backup file should be deleted after disabling backups. Path: {archivePath}, exists: {os.path.exists(archivePath)}", 385 ) 386 387 def add_test_cookie(self): 388 self.marionette.execute_async_script( 389 """ 390 let [outerResolve] = arguments; 391 392 (async () => { 393 // We'll just add a single cookie, and then make sure that it shows 394 // up on the other side. 395 Services.cookies.removeAll(); 396 Services.cookies.add( 397 ".example.com", 398 "/", 399 "first", 400 "one", 401 false, 402 false, 403 false, 404 Date.now() + 1000, 405 {}, 406 Ci.nsICookie.SAMESITE_UNSET, 407 Ci.nsICookie.SCHEME_HTTP 408 ); 409 })().then(outerResolve); 410 """ 411 ) 412 413 def verify_recovered_test_cookie(self): 414 cookiesLength = self.marionette.execute_async_script( 415 """ 416 let [outerResolve] = arguments; 417 (async () => { 418 let cookies = Services.cookies.getCookiesFromHost("example.com", {}); 419 return cookies.length; 420 })().then(outerResolve); 421 """ 422 ) 423 # Expect cookies to be removed from the backup. 424 self.assertEqual(cookiesLength, 0) 425 426 def add_test_login(self): 427 self.marionette.execute_async_script( 428 """ 429 let [outerResolve] = arguments; 430 (async () => { 431 // Let's start with adding a single password 432 Services.logins.removeAllLogins(); 433 434 const nsLoginInfo = new Components.Constructor( 435 "@mozilla.org/login-manager/loginInfo;1", 436 Ci.nsILoginInfo, 437 "init" 438 ); 439 440 const login1 = new nsLoginInfo( 441 "https://example.com", 442 "https://example.com", 443 null, 444 "notifyu1", 445 "notifyp1", 446 "user", 447 "pass" 448 ); 449 await Services.logins.addLoginAsync(login1); 450 })().then(outerResolve); 451 """ 452 ) 453 454 def verify_recovered_test_login(self): 455 loginsLength = self.marionette.execute_async_script( 456 """ 457 let [outerResolve] = arguments; 458 (async () => { 459 let logins = await Services.logins.searchLoginsAsync({ 460 origin: "https://example.com", 461 }); 462 return logins.length; 463 })().then(outerResolve); 464 """ 465 ) 466 self.assertEqual(loginsLength, 1) 467 468 def add_test_certificate(self): 469 certPath = os.path.join(os.path.dirname(__file__), "http2-ca.pem") 470 self.marionette.execute_async_script( 471 """ 472 let [certPath, certDbKey, outerResolve] = arguments; 473 (async () => { 474 const { NetUtil } = ChromeUtils.importESModule( 475 "resource://gre/modules/NetUtil.sys.mjs" 476 ); 477 478 let certDb = Cc["@mozilla.org/security/x509certdb;1"].getService( 479 Ci.nsIX509CertDB 480 ); 481 482 if (certDb.findCertByDBKey(certDbKey)) { 483 throw new Error("Should not have this certificate yet!"); 484 } 485 486 let certFile = await IOUtils.getFile(certPath); 487 let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( 488 Ci.nsIFileInputStream 489 ); 490 fstream.init(certFile, -1, 0, 0); 491 let data = NetUtil.readInputStreamToString(fstream, fstream.available()); 492 fstream.close(); 493 494 let pem = data.replace(/-----BEGIN CERTIFICATE-----/, "") 495 .replace(/-----END CERTIFICATE-----/, "") 496 .replace(/[\\r\\n]/g, ""); 497 let cert = certDb.addCertFromBase64(pem, "CTu,u,u"); 498 499 if (cert.dbKey != certDbKey) { 500 throw new Error("The inserted certificate DB key is unexpected."); 501 } 502 })().then(outerResolve); 503 """, 504 script_args=[certPath, self._cert_db_key], 505 ) 506 507 def verify_recovered_test_certificate(self): 508 certExists = self.marionette.execute_async_script( 509 """ 510 let [certDbKey, outerResolve] = arguments; 511 (async () => { 512 let certDb = Cc["@mozilla.org/security/x509certdb;1"].getService( 513 Ci.nsIX509CertDB 514 ); 515 return certDb.findCertByDBKey(certDbKey) != null; 516 })().then(outerResolve); 517 """, 518 script_args=[self._cert_db_key], 519 ) 520 self.assertTrue(certExists) 521 522 def add_test_saved_address(self): 523 self.marionette.execute_async_script( 524 """ 525 const { formAutofillStorage } = ChromeUtils.importESModule( 526 "resource://autofill/FormAutofillStorage.sys.mjs" 527 ); 528 529 let [outerResolve] = arguments; 530 (async () => { 531 const TEST_ADDRESS_1 = { 532 "given-name": "John", 533 "additional-name": "R.", 534 "family-name": "Smith", 535 organization: "World Wide Web Consortium", 536 "street-address": "32 Vassar Street\\\nMIT Room 32-G524", 537 "address-level2": "Cambridge", 538 "address-level1": "MA", 539 "postal-code": "02139", 540 country: "US", 541 tel: "+15195555555", 542 email: "user@example.com", 543 }; 544 await formAutofillStorage.initialize(); 545 formAutofillStorage.addresses.removeAll(); 546 await formAutofillStorage.addresses.add(TEST_ADDRESS_1); 547 })().then(outerResolve); 548 """ 549 ) 550 551 def verify_recovered_saved_address(self): 552 addressesLength = self.marionette.execute_async_script( 553 """ 554 const { formAutofillStorage } = ChromeUtils.importESModule( 555 "resource://autofill/FormAutofillStorage.sys.mjs" 556 ); 557 558 let [outerResolve] = arguments; 559 (async () => { 560 await formAutofillStorage.initialize(); 561 let addresses = await formAutofillStorage.addresses.getAll(); 562 return addresses.length; 563 })().then(outerResolve); 564 """ 565 ) 566 self.assertEqual(addressesLength, 1) 567 568 def add_test_identity_credential(self): 569 self.marionette.execute_async_script( 570 """ 571 let [outerResolve] = arguments; 572 (async () => { 573 let service = Cc["@mozilla.org/browser/identity-credential-storage-service;1"] 574 .getService(Ci.nsIIdentityCredentialStorageService); 575 service.clear(); 576 577 let testPrincipal = Services.scriptSecurityManager.createContentPrincipal( 578 Services.io.newURI("https://test.com/"), 579 {} 580 ); 581 let idpPrincipal = Services.scriptSecurityManager.createContentPrincipal( 582 Services.io.newURI("https://idp-test.com/"), 583 {} 584 ); 585 586 service.setState( 587 testPrincipal, 588 idpPrincipal, 589 "ID", 590 true, 591 true 592 ); 593 594 })().then(outerResolve); 595 """ 596 ) 597 598 def verify_recovered_identity_credential(self): 599 [registered, allowLogout] = self.marionette.execute_async_script( 600 """ 601 let [outerResolve] = arguments; 602 (async () => { 603 let service = Cc["@mozilla.org/browser/identity-credential-storage-service;1"] 604 .getService(Ci.nsIIdentityCredentialStorageService); 605 606 let testPrincipal = Services.scriptSecurityManager.createContentPrincipal( 607 Services.io.newURI("https://test.com/"), 608 {} 609 ); 610 let idpPrincipal = Services.scriptSecurityManager.createContentPrincipal( 611 Services.io.newURI("https://idp-test.com/"), 612 {} 613 ); 614 615 let registered = {}; 616 let allowLogout = {}; 617 618 service.getState( 619 testPrincipal, 620 idpPrincipal, 621 "ID", 622 registered, 623 allowLogout 624 ); 625 626 return [registered.value, allowLogout.value]; 627 })().then(outerResolve); 628 """ 629 ) 630 self.assertTrue(registered) 631 self.assertTrue(allowLogout) 632 633 def add_test_form_history(self): 634 self.marionette.execute_async_script( 635 """ 636 const { FormHistory } = ChromeUtils.importESModule( 637 "resource://gre/modules/FormHistory.sys.mjs" 638 ); 639 640 let [outerResolve] = arguments; 641 (async () => { 642 await FormHistory.update({ 643 op: "add", 644 fieldname: "some-test-field", 645 value: "I was recovered!", 646 timesUsed: 1, 647 firstUsed: 0, 648 lastUsed: 0, 649 }); 650 651 })().then(outerResolve); 652 """ 653 ) 654 655 def verify_recovered_form_history(self): 656 formHistoryResultsLength = self.marionette.execute_async_script( 657 """ 658 const { FormHistory } = ChromeUtils.importESModule( 659 "resource://gre/modules/FormHistory.sys.mjs" 660 ); 661 662 let [outerResolve] = arguments; 663 (async () => { 664 let results = await FormHistory.search( 665 ["guid"], 666 { fieldname: "some-test-field" } 667 ); 668 return results.length; 669 })().then(outerResolve); 670 """ 671 ) 672 self.assertEqual(formHistoryResultsLength, 1) 673 674 def add_test_asrouter_snippets_data(self): 675 self.marionette.execute_async_script( 676 """ 677 const { ASRouterStorage } = ChromeUtils.importESModule( 678 "resource:///modules/asrouter/ASRouterStorage.sys.mjs", 679 ); 680 const SNIPPETS_TABLE_NAME = "snippets"; 681 682 let [outerResolve] = arguments; 683 (async () => { 684 let storage = new ASRouterStorage({ 685 storeNames: [SNIPPETS_TABLE_NAME], 686 }); 687 let snippetsTable = await storage.getDbTable(SNIPPETS_TABLE_NAME); 688 await snippetsTable.set("backup-test", "some-test-value"); 689 })().then(outerResolve); 690 """ 691 ) 692 693 def verify_recovered_asrouter_snippets_data(self): 694 snippetsResult = self.marionette.execute_async_script( 695 """ 696 const { ASRouterStorage } = ChromeUtils.importESModule( 697 "resource:///modules/asrouter/ASRouterStorage.sys.mjs", 698 ); 699 const SNIPPETS_TABLE_NAME = "snippets"; 700 701 let [outerResolve] = arguments; 702 (async () => { 703 let storage = new ASRouterStorage({ 704 storeNames: [SNIPPETS_TABLE_NAME], 705 }); 706 let snippetsTable = await storage.getDbTable(SNIPPETS_TABLE_NAME); 707 return await snippetsTable.get("backup-test"); 708 })().then(outerResolve); 709 """ 710 ) 711 self.assertEqual(snippetsResult, "some-test-value") 712 713 def add_test_protections_data(self): 714 self.marionette.execute_async_script( 715 """ 716 const TrackingDBService = Cc["@mozilla.org/tracking-db-service;1"] 717 .getService(Ci.nsITrackingDBService); 718 719 let [outerResolve] = arguments; 720 (async () => { 721 let entry = { 722 "https://test.com": [ 723 [Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT, true, 1], 724 ], 725 }; 726 await TrackingDBService.clearAll(); 727 await TrackingDBService.saveEvents(JSON.stringify(entry)); 728 })().then(outerResolve); 729 """ 730 ) 731 732 def verify_recovered_protections_data(self): 733 eventsSum = self.marionette.execute_async_script( 734 """ 735 const TrackingDBService = Cc["@mozilla.org/tracking-db-service;1"] 736 .getService(Ci.nsITrackingDBService); 737 738 let [outerResolve] = arguments; 739 (async () => { 740 return TrackingDBService.sumAllEvents(); 741 })().then(outerResolve); 742 """ 743 ) 744 self.assertEqual(eventsSum, 1) 745 746 def add_test_bookmarks(self): 747 self.marionette.execute_async_script( 748 """ 749 const { PlacesUtils } = ChromeUtils.importESModule( 750 "resource://gre/modules/PlacesUtils.sys.mjs" 751 ); 752 753 let [outerResolve] = arguments; 754 (async () => { 755 await PlacesUtils.bookmarks.eraseEverything(); 756 await PlacesUtils.bookmarks.insert({ 757 parentGuid: PlacesUtils.bookmarks.toolbarGuid, 758 title: "Some test page", 759 url: Services.io.newURI("https://www.backup.test/"), 760 }); 761 })().then(outerResolve); 762 """ 763 ) 764 765 def verify_recovered_bookmarks(self): 766 bookmarkExists = self.marionette.execute_async_script( 767 """ 768 const { PlacesUtils } = ChromeUtils.importESModule( 769 "resource://gre/modules/PlacesUtils.sys.mjs" 770 ); 771 772 let [outerResolve] = arguments; 773 (async () => { 774 let url = Services.io.newURI("https://www.backup.test/"); 775 let bookmark = await PlacesUtils.bookmarks.fetch({ url }); 776 return bookmark != null; 777 })().then(outerResolve); 778 """ 779 ) 780 self.assertTrue(bookmarkExists) 781 782 def add_test_history(self): 783 self.marionette.execute_async_script( 784 """ 785 const { PlacesUtils } = ChromeUtils.importESModule( 786 "resource://gre/modules/PlacesUtils.sys.mjs" 787 ); 788 789 let [outerResolve] = arguments; 790 (async () => { 791 await PlacesUtils.history.clear(); 792 793 let entry = { 794 url: "http://my-restored-history.com", 795 visits: [{ transition: PlacesUtils.history.TRANSITION_LINK }], 796 }; 797 798 await PlacesUtils.history.insertMany([entry]); 799 })().then(outerResolve); 800 """ 801 ) 802 803 def verify_recovered_history(self): 804 historyExists = self.marionette.execute_async_script( 805 """ 806 const { PlacesUtils } = ChromeUtils.importESModule( 807 "resource://gre/modules/PlacesUtils.sys.mjs" 808 ); 809 810 let [outerResolve] = arguments; 811 (async () => { 812 let entry = await PlacesUtils.history.fetch("http://my-restored-history.com"); 813 return entry != null; 814 })().then(outerResolve); 815 """ 816 ) 817 self.assertTrue(historyExists) 818 819 def add_test_preferences(self): 820 self.marionette.execute_script( 821 """ 822 Services.prefs.setBoolPref("test-pref-for-backup", true) 823 """ 824 ) 825 826 def verify_recovered_preferences(self): 827 prefExists = self.marionette.execute_script( 828 """ 829 return Services.prefs.getBoolPref("test-pref-for-backup", false); 830 """ 831 ) 832 self.assertTrue(prefExists) 833 834 def add_test_permissions(self): 835 self.marionette.execute_script( 836 """ 837 let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin( 838 "https://test-permission-site.com" 839 ); 840 Services.perms.addFromPrincipal( 841 principal, 842 "desktop-notification", 843 Services.perms.ALLOW_ACTION 844 ); 845 """ 846 ) 847 848 def verify_recovered_permissions(self): 849 permissionExists = self.marionette.execute_script( 850 """ 851 let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin( 852 "https://test-permission-site.com" 853 ); 854 let perms = Services.perms.getAllForPrincipal(principal); 855 if (perms.length != 1) { 856 throw new Error("Got an unexpected number of permissions"); 857 } 858 return perms[0].type == "desktop-notification" 859 """ 860 ) 861 self.assertTrue(permissionExists) 862 863 def add_test_payment_methods(self): 864 self.marionette.execute_async_script( 865 """ 866 const { formAutofillStorage } = ChromeUtils.importESModule( 867 "resource://autofill/FormAutofillStorage.sys.mjs" 868 ); 869 870 let [outerResolve] = arguments; 871 (async () => { 872 await formAutofillStorage.initialize(); 873 await formAutofillStorage.creditCards.add({ 874 "cc-name": "Foxy the Firefox", 875 "cc-number": "5555555555554444", 876 "cc-exp-month": 5, 877 "cc-exp-year": 2099, 878 }); 879 })().then(outerResolve); 880 """ 881 ) 882 883 def verify_recovered_payment_methods(self, osKeyStoreLabel): 884 cardExists = self.marionette.execute_async_script( 885 """ 886 const { formAutofillStorage } = ChromeUtils.importESModule( 887 "resource://autofill/FormAutofillStorage.sys.mjs" 888 ); 889 let nativeOSKeyStore = Cc["@mozilla.org/security/oskeystore;1"].getService( 890 Ci.nsIOSKeyStore 891 ); 892 893 let [osKeyStoreLabel, outerResolve] = arguments; 894 895 (async () => { 896 await formAutofillStorage.initialize(); 897 let cards = await formAutofillStorage.creditCards.getAll(); 898 899 if (cards.length != 1) { 900 return false; 901 } 902 let card = cards[0]; 903 if (card["cc-name"] != "Foxy the Firefox") { 904 return false; 905 } 906 907 if (card["cc-exp-month"] != "5") { 908 return false; 909 } 910 911 if (card["cc-exp-year"] != "2099") { 912 return false; 913 } 914 915 if (!card["cc-number-encrypted"]) { 916 return false; 917 } 918 919 // Hack around OSKeyStore's insistence on asking for 920 // reauthentication by using the underlying nativeOSKeyStore 921 // to decrypt the credit card number to check it. 922 let plaintextCardBytes = 923 await nativeOSKeyStore.asyncDecryptBytes( 924 osKeyStoreLabel, 925 card["cc-number-encrypted"] 926 ); 927 let plaintextCard = String.fromCharCode.apply( 928 String, 929 plaintextCardBytes 930 ); 931 if (plaintextCard != "5555555555554444") { 932 return false; 933 } 934 935 return true; 936 })().then(outerResolve); 937 """, 938 script_args=[osKeyStoreLabel], 939 ) 940 self.assertTrue(cardExists) 941 942 def add_test_sessionstore(self): 943 with self.marionette.using_context("content"): 944 self.marionette.navigate("about:mozilla") 945 946 def verify_recovered_sessionstore(self): 947 [tabCount, url] = self.marionette.execute_script( 948 """ 949 const { SessionStore } = ChromeUtils.importESModule( 950 "resource:///modules/sessionstore/SessionStore.sys.mjs" 951 ); 952 const session = SessionStore.getCurrentState(true); 953 const win = session.windows[0]; 954 const tabLen = win.tabs.length; 955 const tab = win.tabs[0]; 956 const entry = tab.entries[0]; 957 const url = entry.url; 958 return [tabLen, url]; 959 """ 960 ) 961 962 self.assertEqual(tabCount, 1) 963 self.assertEqual(url, "about:mozilla")