tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_BackupService_enabled.js (13416B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 https://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 const { ExperimentAPI } = ChromeUtils.importESModule(
      7  "resource://nimbus/ExperimentAPI.sys.mjs"
      8 );
      9 const { NimbusTestUtils } = ChromeUtils.importESModule(
     10  "resource://testing-common/NimbusTestUtils.sys.mjs"
     11 );
     12 
     13 const BACKUP_DIR_PREF_NAME = "browser.backup.location";
     14 const BACKUP_ARCHIVE_ENABLED_PREF_NAME = "browser.backup.archive.enabled";
     15 const BACKUP_ARCHIVE_ENABLED_OVERRIDE_PREF_NAME =
     16  "browser.backup.archive.overridePlatformCheck";
     17 const BACKUP_RESTORE_ENABLED_PREF_NAME = "browser.backup.restore.enabled";
     18 const BACKUP_RESTORE_ENABLED_OVERRIDE_PREF_NAME =
     19  "browser.backup.restore.overridePlatformCheck";
     20 const SELECTABLE_PROFILES_CREATED_PREF_NAME = "browser.profiles.created";
     21 
     22 add_setup(async () => {
     23  setupProfile();
     24 
     25  NimbusTestUtils.init(this);
     26 
     27  const { cleanup: nimbusCleanup } = await NimbusTestUtils.setupTest();
     28 
     29  await ExperimentAPI.ready();
     30 
     31  let backupDir = await IOUtils.createUniqueDirectory(
     32    PathUtils.profileDir,
     33    "backup"
     34  );
     35 
     36  // Use temporary directory for backups.
     37  Services.prefs.setStringPref(BACKUP_DIR_PREF_NAME, backupDir);
     38 
     39  registerCleanupFunction(async () => {
     40    const nimbusCleanupPromise = nimbusCleanup();
     41    const backupDirCleanupPromise = IOUtils.remove(backupDir, {
     42      recursive: true,
     43    });
     44 
     45    await Promise.all([nimbusCleanupPromise, backupDirCleanupPromise]);
     46 
     47    Services.prefs.clearUserPref(BACKUP_DIR_PREF_NAME);
     48  });
     49 });
     50 
     51 add_task(async function test_archive_killswitch_enrollment() {
     52  let cleanupExperiment;
     53  const savedPref = Services.prefs.getBoolPref(
     54    BACKUP_ARCHIVE_ENABLED_OVERRIDE_PREF_NAME,
     55    false
     56  );
     57  await archiveTemplate({
     58    internalReason: "nimbus",
     59    async disable() {
     60      Services.prefs.setBoolPref(
     61        BACKUP_ARCHIVE_ENABLED_OVERRIDE_PREF_NAME,
     62        false
     63      );
     64      cleanupExperiment = await NimbusTestUtils.enrollWithFeatureConfig({
     65        featureId: "backupService",
     66        value: { archiveKillswitch: true },
     67      });
     68    },
     69    async enable() {
     70      Services.prefs.setBoolPref(
     71        BACKUP_ARCHIVE_ENABLED_OVERRIDE_PREF_NAME,
     72        savedPref
     73      );
     74      await cleanupExperiment();
     75    },
     76    // Nimbus calls onUpdate if any experiments are running, meaning that the
     77    // observer service will be notified twice, i.e. one spurious call.
     78    startup: 0,
     79  });
     80 });
     81 
     82 add_task(async function test_archive_enabled_pref() {
     83  await archiveTemplate({
     84    internalReason: "pref",
     85    async disable() {
     86      Services.prefs.unlockPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME);
     87      Services.prefs.setBoolPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME, false);
     88    },
     89    async enable() {
     90      Services.prefs.setBoolPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME, true);
     91    },
     92  });
     93 });
     94 
     95 add_task(async function test_archive_policy() {
     96  let storedDefault;
     97  await archiveTemplate({
     98    internalReason: "policy",
     99    disable: () => {
    100      Services.prefs.unlockPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME);
    101      const defaults = Services.prefs.getDefaultBranch("");
    102      storedDefault = defaults.getBoolPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME);
    103      defaults.setBoolPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME, false);
    104      defaults.lockPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME);
    105      return 0;
    106    },
    107    enable: () => {
    108      const defaults = Services.prefs.getDefaultBranch("");
    109      defaults.setBoolPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME, storedDefault);
    110      Services.prefs.unlockPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME);
    111      Services.prefs.setBoolPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME, true);
    112      return 0;
    113    },
    114    // At startup, there wouldn't have been a spurious call.
    115    startup: 0,
    116  });
    117 });
    118 
    119 add_task(async function test_archive_selectable_profiles() {
    120  await archiveTemplate({
    121    internalReason: "selectable profiles",
    122    async disable() {
    123      Services.prefs.setBoolPref(SELECTABLE_PROFILES_CREATED_PREF_NAME, true);
    124    },
    125    async enable() {
    126      Services.prefs.setBoolPref(SELECTABLE_PROFILES_CREATED_PREF_NAME, false);
    127    },
    128  });
    129 });
    130 
    131 add_task(async function test_archive_disabled_unsupported_os() {
    132  const sandbox = sinon.createSandbox();
    133  const archiveWasEnabled = Services.prefs.getBoolPref(
    134    BACKUP_ARCHIVE_ENABLED_OVERRIDE_PREF_NAME,
    135    false
    136  );
    137  Services.prefs.setBoolPref(BACKUP_ARCHIVE_ENABLED_OVERRIDE_PREF_NAME, false);
    138  sandbox.stub(BackupService, "checkOsSupportsBackup").returns(false);
    139  const cleanupExperiment = await NimbusTestUtils.enrollWithFeatureConfig({
    140    featureId: "backupService",
    141    value: { archiveKillswitch: false },
    142  });
    143 
    144  try {
    145    const bs = new BackupService();
    146    const status = bs.archiveEnabledStatus;
    147    Assert.equal(false, status.enabled);
    148    Assert.equal("os version", status.internalReason);
    149  } finally {
    150    sandbox.restore();
    151    Services.prefs.setBoolPref(
    152      BACKUP_ARCHIVE_ENABLED_OVERRIDE_PREF_NAME,
    153      archiveWasEnabled
    154    );
    155    await cleanupExperiment();
    156  }
    157 });
    158 
    159 add_task(async function test_archive_enabled_supported_os() {
    160  const sandbox = sinon.createSandbox();
    161  const archiveWasEnabled = Services.prefs.getBoolPref(
    162    BACKUP_ARCHIVE_ENABLED_OVERRIDE_PREF_NAME,
    163    false
    164  );
    165  Services.prefs.setBoolPref(BACKUP_ARCHIVE_ENABLED_OVERRIDE_PREF_NAME, false);
    166  sandbox.stub(BackupService, "checkOsSupportsBackup").returns(true);
    167  const cleanupExperiment = await NimbusTestUtils.enrollWithFeatureConfig({
    168    featureId: "backupService",
    169    value: { archiveKillswitch: false },
    170  });
    171  try {
    172    const bs = new BackupService();
    173    const status = bs.archiveEnabledStatus;
    174    Assert.equal(true, status.enabled);
    175  } finally {
    176    sandbox.restore();
    177    Services.prefs.setBoolPref(
    178      BACKUP_ARCHIVE_ENABLED_OVERRIDE_PREF_NAME,
    179      archiveWasEnabled
    180    );
    181    await cleanupExperiment();
    182  }
    183 });
    184 
    185 add_task(async function test_restore_killswitch_enrollment() {
    186  let cleanupExperiment;
    187  const savedPref = Services.prefs.getBoolPref(
    188    BACKUP_RESTORE_ENABLED_OVERRIDE_PREF_NAME,
    189    false
    190  );
    191  await restoreTemplate({
    192    internalReason: "nimbus",
    193    async disable() {
    194      Services.prefs.setBoolPref(
    195        BACKUP_RESTORE_ENABLED_OVERRIDE_PREF_NAME,
    196        false
    197      );
    198      cleanupExperiment = await NimbusTestUtils.enrollWithFeatureConfig({
    199        featureId: "backupService",
    200        value: { restoreKillswitch: true },
    201      });
    202    },
    203    async enable() {
    204      Services.prefs.setBoolPref(
    205        BACKUP_RESTORE_ENABLED_OVERRIDE_PREF_NAME,
    206        savedPref
    207      );
    208      await cleanupExperiment();
    209    },
    210    // Nimbus calls onUpdate if any experiments are running, meaning that the
    211    // observer service will be notified twice, i.e. one spurious call.
    212    startup: 0,
    213  });
    214 });
    215 
    216 add_task(async function test_restore_enabled_pref() {
    217  await restoreTemplate({
    218    internalReason: "pref",
    219    async disable() {
    220      Services.prefs.setBoolPref(BACKUP_RESTORE_ENABLED_PREF_NAME, false);
    221    },
    222    async enable() {
    223      Services.prefs.setBoolPref(BACKUP_RESTORE_ENABLED_PREF_NAME, true);
    224    },
    225  });
    226 });
    227 
    228 add_task(async function test_restore_policy() {
    229  let storedDefault;
    230  await restoreTemplate({
    231    internalReason: "policy",
    232    async disable() {
    233      const defaults = Services.prefs.getDefaultBranch("");
    234      storedDefault = defaults.getBoolPref(BACKUP_RESTORE_ENABLED_PREF_NAME);
    235      defaults.setBoolPref(BACKUP_RESTORE_ENABLED_PREF_NAME, false);
    236      Services.prefs.lockPref(BACKUP_RESTORE_ENABLED_PREF_NAME);
    237      return 0;
    238    },
    239    async enable() {
    240      Services.prefs.unlockPref(BACKUP_RESTORE_ENABLED_PREF_NAME);
    241      const defaults = Services.prefs.getDefaultBranch("");
    242      defaults.setBoolPref(BACKUP_RESTORE_ENABLED_PREF_NAME, storedDefault);
    243      return 0;
    244    },
    245    // At startup, there wouldn't have been a spurious call.
    246    startup: 0,
    247  });
    248 });
    249 
    250 add_task(async function test_restore_selectable_profiles() {
    251  await restoreTemplate({
    252    internalReason: "selectable profiles",
    253    async disable() {
    254      Services.prefs.setBoolPref(SELECTABLE_PROFILES_CREATED_PREF_NAME, true);
    255    },
    256    async enable() {
    257      Services.prefs.setBoolPref(SELECTABLE_PROFILES_CREATED_PREF_NAME, false);
    258    },
    259  });
    260 });
    261 
    262 async function archiveTemplate({ internalReason, disable, enable, startup }) {
    263  Services.telemetry.clearScalars();
    264  Services.fog.testResetFOG();
    265 
    266  let bs = new BackupService();
    267  bs.initStatusObservers();
    268  assertStatus("archive", bs.archiveEnabledStatus, true, null);
    269 
    270  let calledCount = 0;
    271  let callback = () => calledCount++;
    272  Services.obs.addObserver(callback, "backup-service-status-updated");
    273 
    274  let spurious = (await disable()) ?? 0;
    275  Assert.equal(calledCount, 1 + spurious, "Observers were notified on disable");
    276  assertStatus("archive", bs.archiveEnabledStatus, false, internalReason);
    277 
    278  let backup = await bs.createBackup();
    279  Assert.ok(
    280    !backup,
    281    "Creating a backup should fail when archiving is disabled."
    282  );
    283 
    284  spurious += (await enable()) ?? 0;
    285  Assert.equal(
    286    calledCount,
    287    2 + spurious,
    288    "Observers were notified on re-enable"
    289  );
    290  assertStatus("archive", bs.archiveEnabledStatus, true, "reenabled");
    291 
    292  backup = await bs.createBackup();
    293  ok(
    294    backup,
    295    "Creating a backup should succeed once the archive killswitch experiment ends."
    296  );
    297  ok(
    298    await IOUtils.exists(backup.archivePath),
    299    "Archive file should exist on disk."
    300  );
    301 
    302  await IOUtils.remove(backup.archivePath);
    303  bs.uninitStatusObservers();
    304 
    305  // Also check that it works at startup.
    306  spurious += (startup ?? 0) + ((await disable()) ?? 0);
    307  bs = new BackupService();
    308  bs.initStatusObservers();
    309  Assert.equal(calledCount, 3 + spurious, "Observers were notified at startup");
    310  assertStatus("archive", bs.archiveEnabledStatus, false, internalReason);
    311  await enable();
    312  bs.uninitStatusObservers();
    313 
    314  Services.obs.removeObserver(callback, "backup-service-status-updated");
    315 }
    316 
    317 async function restoreTemplate({ internalReason, disable, enable, startup }) {
    318  Services.telemetry.clearScalars();
    319  Services.fog.testResetFOG();
    320 
    321  let bs = new BackupService();
    322  bs.initStatusObservers();
    323  assertStatus("restore", bs.restoreEnabledStatus, true, null);
    324 
    325  const backup = await bs.createBackup();
    326  Assert.ok(
    327    backup && backup.archivePath,
    328    "Archive should have been created on disk."
    329  );
    330 
    331  let calledCount = 0;
    332  let callback = () => calledCount++;
    333  Services.obs.addObserver(callback, "backup-service-status-updated");
    334 
    335  let spurious = (await disable()) ?? 0;
    336  Assert.equal(calledCount, 1 + spurious, "Observers were notified on disable");
    337  assertStatus("restore", bs.restoreEnabledStatus, false, internalReason);
    338 
    339  const recoveryDir = await IOUtils.createUniqueDirectory(
    340    PathUtils.profileDir,
    341    "recovered-profiles"
    342  );
    343 
    344  await Assert.rejects(
    345    bs.recoverFromBackupArchive(
    346      backup.archivePath,
    347      null,
    348      false,
    349      PathUtils.profileDir,
    350      recoveryDir
    351    ),
    352    /.*disabled.*/,
    353    "Recovery should throw when the restore is disabled."
    354  );
    355 
    356  spurious += (await enable()) ?? 0;
    357  Assert.equal(
    358    calledCount,
    359    2 + spurious,
    360    "Observers were notified on re-enable"
    361  );
    362  assertStatus("restore", bs.restoreEnabledStatus, true, "reenabled");
    363 
    364  let recoveredProfile = await bs.recoverFromBackupArchive(
    365    backup.archivePath,
    366    null,
    367    false,
    368    PathUtils.profileDir,
    369    recoveryDir
    370  );
    371  Assert.ok(
    372    recoveredProfile,
    373    "Recovery should succeed once restore is re-enabled."
    374  );
    375  Assert.ok(
    376    await IOUtils.exists(recoveredProfile.rootDir.path),
    377    "Recovered profile directory should exist on disk."
    378  );
    379 
    380  bs.uninitStatusObservers();
    381 
    382  // Also check that it works at startup.
    383  spurious += (startup ?? 0) + ((await disable()) ?? 0);
    384  bs = new BackupService();
    385  bs.initStatusObservers();
    386  Assert.equal(calledCount, 3 + spurious, "Observers were notified at startup");
    387  assertStatus("restore", bs.restoreEnabledStatus, false, internalReason);
    388  await enable();
    389  bs.uninitStatusObservers();
    390 
    391  Services.obs.removeObserver(callback, "backup-service-status-updated");
    392 }
    393 
    394 /**
    395 * Checks that the status object matches the expected values, and that the
    396 * telemetry matches the object.
    397 *
    398 * @param {string} kind
    399 *   The kind of status object.
    400 * @param {string} status
    401 *   The status object given.
    402 * @param {boolean} enabled
    403 *   Whether the feature should be enabled or disabled.
    404 * @param {string?} internalReason
    405 *   The internal reason that should be given.
    406 */
    407 function assertStatus(kind, status, enabled, internalReason) {
    408  Assert.equal(
    409    status.enabled,
    410    enabled,
    411    `${kind} status is ${enabled ? "" : "not "}enabled.`
    412  );
    413  Assert.equal(
    414    status.internalReason ?? null,
    415    // 'reenabled' is only for telemetry, the status is null
    416    internalReason == "reenabled" ? null : internalReason,
    417    `${kind} status has the expected internal reason.`
    418  );
    419 
    420  Assert.equal(
    421    Glean.browserBackup[kind + "Enabled"].testGetValue(),
    422    enabled,
    423    `Glean ${kind}_enabled metric should be ${enabled}.`
    424  );
    425  TelemetryTestUtils.assertScalar(
    426    TelemetryTestUtils.getProcessScalars("parent", false, true),
    427    `browser.backup.${kind}_enabled`,
    428    enabled,
    429    `Legacy ${kind}_enabled metric should be ${enabled}.`
    430  );
    431 
    432  Assert.equal(
    433    Glean.browserBackup[kind + "DisabledReason"].testGetValue(),
    434    internalReason,
    435    `Glean ${kind}_disabled_reason metric is ${internalReason}.`
    436  );
    437 }