chromium/chrome/browser/ui/webui/ash/settings/integration_tests/os_settings_recovery_browsertest.cc

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/webui/settings/public/constants/setting.mojom-shared.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ui/webui/ash/settings/test_support/os_settings_lock_screen_browser_test_base.h"
#include "chrome/test/data/webui/chromeos/settings/test_api.test-mojom-test-utils.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_types.h"
#include "content/public/test/browser_test.h"

namespace {

const char kRecoveryFactorBehaviorPolicy[] = "RecoveryFactorBehavior";

}

namespace ash::settings {

class OSSettingsRecoveryTest : public OSSettingsLockScreenBrowserTestBase {};

// A test fixture that runs tests with recovery feature enabled but without
// hardware support.
class OSSettingsRecoveryTestWithoutHardwareSupport
    : public OSSettingsRecoveryTest {
 public:
  OSSettingsRecoveryTestWithoutHardwareSupport() {
    cryptohome_->set_supports_low_entropy_credentials(false);
  }
};

IN_PROC_BROWSER_TEST_F(OSSettingsRecoveryTestWithoutHardwareSupport,
                       ControlInvisibleNotAvailable) {
  mojom::LockScreenSettingsAsyncWaiter lock_screen_settings =
      OpenLockScreenSettingsAndAuthenticate();
  lock_screen_settings.AssertRecoveryControlVisibility(true);
  lock_screen_settings.AssertRecoveryControlAvailability(false);
}

IN_PROC_BROWSER_TEST_F(OSSettingsRecoveryTest, ControlVisible) {
  mojom::LockScreenSettingsAsyncWaiter lock_screen_settings =
      OpenLockScreenSettingsAndAuthenticate();
  lock_screen_settings.AssertRecoveryControlVisibility(true);
  lock_screen_settings.AssertRecoveryControlAvailability(true);
}

IN_PROC_BROWSER_TEST_F(OSSettingsRecoveryTest, CheckingEnables) {
  EXPECT_FALSE(cryptohome_->HasRecoveryFactor(GetAccountId()));

  mojom::LockScreenSettingsAsyncWaiter lock_screen_settings =
      OpenLockScreenSettingsAndAuthenticate();
  lock_screen_settings.AssertRecoveryConfigured(false);
  lock_screen_settings.EnableRecoveryConfiguration();
  lock_screen_settings.AssertRecoveryConfigured(true);

  EXPECT_TRUE(cryptohome_->HasRecoveryFactor(GetAccountId()));
}

// The following test sets the cryptohome recovery toggle to "on".
// It clicks on the recovery toggle, expecting the recovery dialog to show up.
// It then clicks on the cancel button of the dialog.
// Expected result: The dialog disappears and the toggle is still on.
IN_PROC_BROWSER_TEST_F(OSSettingsRecoveryTest,
                       UncheckingDisablesAndCancelClick) {
  cryptohome_->AddRecoveryFactor(GetAccountId());

  mojom::LockScreenSettingsAsyncWaiter lock_screen_settings =
      OpenLockScreenSettingsAndAuthenticate();
  lock_screen_settings.AssertRecoveryConfigured(true);
  lock_screen_settings.DisableRecoveryConfiguration(
      mojom::LockScreenSettings::RecoveryDialogAction::CancelDialog);
  lock_screen_settings.AssertRecoveryConfigured(true);
  // After the CancelClick on the dialog, the recovery configuration
  // should remain enabled.
  EXPECT_TRUE(cryptohome_->HasRecoveryFactor(GetAccountId()));
}

// The following test sets the cryptohome recovery toggle to "on".
// It clicks on the recovery toggle, expecting the recovery dialog to show up.
// It then clicks on the disable button of the dialog.
// Expected result: The dialog disappears and the toggle is off.
IN_PROC_BROWSER_TEST_F(OSSettingsRecoveryTest,
                       UncheckingDisablesAndDisableClick) {
  cryptohome_->AddRecoveryFactor(GetAccountId());

  mojom::LockScreenSettingsAsyncWaiter lock_screen_settings =
      OpenLockScreenSettingsAndAuthenticate();
  lock_screen_settings.AssertRecoveryConfigured(true);
  lock_screen_settings.DisableRecoveryConfiguration(
      mojom::LockScreenSettings::RecoveryDialogAction::ConfirmDisabling);
  lock_screen_settings.AssertRecoveryConfigured(false);

  EXPECT_FALSE(cryptohome_->HasRecoveryFactor(GetAccountId()));
}

// Check that the kDataRecovery deep link id can navigate to
// the recovery toggle.
IN_PROC_BROWSER_TEST_F(OSSettingsRecoveryTest, NavigaionToRecoveryToggle) {
  cryptohome_->AddRecoveryFactor(GetAccountId());
  mojom::LockScreenSettingsAsyncWaiter lock_screen_settings =
      OpenLockScreenSettingsDeepLinkAndAuthenticate(base::NumberToString(
          static_cast<int>(chromeos::settings::mojom::Setting::kDataRecovery)));
  lock_screen_settings.AssertRecoveryControlFocused();
}

// Check that trying to change recovery with an invalidated auth session shows
// the password prompt again.
// TODO(crbug.com/40906200): Re-enable this test
IN_PROC_BROWSER_TEST_F(OSSettingsRecoveryTest, DISABLED_DestroyedSession) {
  mojom::LockScreenSettingsAsyncWaiter lock_screen_settings =
      OpenLockScreenSettingsAndAuthenticate();

  // Try to change recovery setting, but with an invalid auth session. This
  // should throw us back to the password prompt.
  cryptohome_->DestroySessions();
  lock_screen_settings.TryEnableRecoveryConfiguration();
  lock_screen_settings.AssertAuthenticated(false);

  // Check that it's still possible to authenticate and change recovery
  // settings.
  EXPECT_FALSE(cryptohome_->HasRecoveryFactor(GetAccountId()));
  lock_screen_settings.Authenticate(kPassword);
  lock_screen_settings.EnableRecoveryConfiguration();
  EXPECT_TRUE(cryptohome_->HasRecoveryFactor(GetAccountId()));
}

struct CryptohomeRecoveryPolicySetting {
  bool value;
  bool is_recommendation;
};

class OSSettingsRecoveryTestWithPolicy : public OSSettingsRecoveryTest {
 public:
  void SetUpInProcessBrowserTestFixture() override {
    OSSettingsRecoveryTest::SetUpInProcessBrowserTestFixture();

    // Override and policy provider for testing. The `ON_CALL` lines here are
    // necessary because something inside the policy stack expects those return
    // values.
    ON_CALL(provider_, IsInitializationComplete(testing::_))
        .WillByDefault(testing::Return(true));
    ON_CALL(provider_, IsFirstPolicyLoadComplete(testing::_))
        .WillByDefault(testing::Return(true));
    policy::BrowserPolicyConnector::SetPolicyProviderForTesting(&provider_);
  }

  void SetCryptohomeRecoveryPolicy(policy::PolicyLevel level, bool value) {
    policy::PolicyMap policies;
    policies.Set(kRecoveryFactorBehaviorPolicy, level,
                 policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
                 base::Value(value),
                 /*external_data_fetcher=*/nullptr);
    provider_.UpdateChromePolicy(policies);
  }

 private:
  testing::NiceMock<policy::MockConfigurationPolicyProvider> provider_;
};

// Check that the recovery toggle cannot be flipped to "on" if a policy
// mandates that cryptohome recovery is disabled.
IN_PROC_BROWSER_TEST_F(OSSettingsRecoveryTestWithPolicy, DisabledMandatory) {
  SetCryptohomeRecoveryPolicy(policy::POLICY_LEVEL_MANDATORY, false);

  mojom::LockScreenSettingsAsyncWaiter lock_screen_settings =
      OpenLockScreenSettingsAndAuthenticate();
  lock_screen_settings.TryEnableRecoveryConfiguration();

  // This will repeateadly check that the recovery toggle is set to "off" for
  // some time, so we would catch if the toggle flips asynchronously after some
  // time.
  lock_screen_settings.AssertRecoveryConfigured(false);
  EXPECT_FALSE(cryptohome_->HasRecoveryFactor(GetAccountId()));
}

// Check that the recovery toggle can be flipped if a policy mandates that
// recovery is enabled but recovery is currently not enabled.
IN_PROC_BROWSER_TEST_F(OSSettingsRecoveryTestWithPolicy,
                       EnabledMandatoryButDisabled) {
  SetCryptohomeRecoveryPolicy(policy::POLICY_LEVEL_MANDATORY, true);
  EXPECT_FALSE(cryptohome_->HasRecoveryFactor(GetAccountId()));

  mojom::LockScreenSettingsAsyncWaiter lock_screen_settings =
      OpenLockScreenSettingsAndAuthenticate();
  lock_screen_settings.AssertRecoveryConfigured(false);

  lock_screen_settings.EnableRecoveryConfiguration();

  lock_screen_settings.AssertRecoveryConfigured(true);
  EXPECT_TRUE(cryptohome_->HasRecoveryFactor(GetAccountId()));

  // Try to disable again -- this should have no effect, because the policy
  // mandates that recovery must be configured.
  lock_screen_settings.TryDisableRecoveryConfiguration();

  // This will repeateadly check that the recovery toggle is set to "on" for
  // some time, so we would catch if the toggle flips asynchronously after some
  // time.
  lock_screen_settings.AssertRecoveryConfigured(true);
  EXPECT_TRUE(cryptohome_->HasRecoveryFactor(GetAccountId()));
}

}  // namespace ash::settings