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

#include <algorithm>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/system_tray_test_api.h"
#include "ash/shell.h"
#include "base/command_line.h"
#include "base/functional/callback.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/run_until.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/test/values_test_util.h"
#include "base/types/expected.h"
#include "chrome/browser/ash/login/login_manager_test.h"
#include "chrome/browser/ash/login/test/login_manager_mixin.h"
#include "chrome/browser/policy/networking/network_configuration_updater.h"
#include "chrome/browser/ui/ash/network/enrollment_dialog_view.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/ash/scoped_test_system_nss_key_slot_mixin.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chromeos/ash/components/dbus/shill/shill_device_client.h"
#include "chromeos/ash/components/dbus/shill/shill_ipconfig_client.h"
#include "chromeos/ash/components/dbus/shill/shill_manager_client.h"
#include "chromeos/ash/components/dbus/shill/shill_profile_client.h"
#include "chromeos/ash/components/dbus/shill/shill_property_changed_observer.h"
#include "chromeos/ash/components/dbus/shill/shill_service_client.h"
#include "chromeos/ash/components/install_attributes/stub_install_attributes.h"
#include "chromeos/ash/components/network/managed_network_configuration_handler.h"
#include "chromeos/ash/components/network/network_cert_loader.h"
#include "chromeos/ash/components/network/network_connection_handler.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_policy_observer.h"
#include "chromeos/ash/components/system/fake_statistics_provider.h"
#include "chromeos/ash/services/network_config/cros_network_config.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "chromeos/services/network_config/public/cpp/cros_network_config_observer.h"
#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
#include "components/account_id/account_id.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/policy_constants.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "crypto/scoped_nss_types.h"
#include "dbus/object_path.h"
#include "net/cert/cert_database.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_data_directory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/test/event_generator.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/view.h"
#include "ui/views/view_observer.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/any_widget_observer.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/widget/widget_utils.h"
#include "ui/views/window/dialog_delegate.h"

namespace policy {

namespace {

namespace network_mojom = ::chromeos::network_config::mojom;
using ::base::test::DictionaryHasValue;
using ::base::test::DictionaryHasValues;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::IsEmpty;
using ::testing::IsNull;
using ::testing::Not;
using ::testing::Pointee;
using ::testing::SizeIs;

constexpr char kUserProfilePath[] = "user_profile";
constexpr char kSharedProfilePath[] = "/profile/default";
constexpr char kDeviceWifiPath[] = "/device/wifi1";
constexpr char kServiceEth[] = "/service/0";
constexpr char kServiceEth1[] = "/service/5";
constexpr char kServiceEth2[] = "/service/6";
constexpr char kServiceWifi1[] = "/service/1";
constexpr char kServiceWifi2[] = "/service/2";
constexpr char kServiceWifi3[] = "/service/3";
constexpr char kServiceWifi4[] = "/service/4";

constexpr char kUIDataKeyUserSettings[] = "user_settings";

constexpr char kUserIdentity[] = "user_identity";

constexpr char kTestDomain[] = "test_domain";
constexpr char kTestDeviceId[] = "test_device_id";

// Records all values that shill service property had during the lifetime of
// ServicePropertyValueWatcher. Only supports string properties at the moment.
class ServicePropertyValueWatcher : public ash::ShillPropertyChangedObserver {
 public:
  ServicePropertyValueWatcher(
      ash::ShillServiceClient::TestInterface* shill_service_client_test,
      const std::string& service_path,
      const std::string& property_name)
      : shill_service_client_test_(shill_service_client_test),
        service_path_(service_path),
        property_name_(property_name) {
    ash::ShillServiceClient::Get()->AddPropertyChangedObserver(
        dbus::ObjectPath(service_path_), this);

    // If the service already exists and has `property_name`, record the initial
    // value.
    const base::DictValue* initial_service_properties =
        shill_service_client_test_->GetServiceProperties(service_path);
    if (!initial_service_properties) {
      return;
    }
    const std::string* property_value =
        initial_service_properties->FindString(property_name);
    if (!property_value) {
      return;
    }
    values_.push_back(*property_value);
  }

  ~ServicePropertyValueWatcher() override {
    ash::ShillServiceClient::Get()->RemovePropertyChangedObserver(
        dbus::ObjectPath(service_path_), this);
  }

  ServicePropertyValueWatcher(const ServicePropertyValueWatcher&) = delete;
  ServicePropertyValueWatcher& operator=(const ServicePropertyValueWatcher&) =
      delete;

  void OnPropertyChanged(const std::string& name,
                         const base::Value& value) override {
    if (name != property_name_) {
      return;
    }
    if (!value.is_string()) {
      return;
    }
    if (!values_.empty() && values_.back() == value.GetString()) {
      return;
    }
    values_.push_back(value.GetString());
    if (wait_for_value_state_ &&
        wait_for_value_state_->predicate.Run(value.GetString())) {
      wait_for_value_state_->run_loop.Quit();
    }
  }

  // Returns all values that the property passed to the constructor had since
  // this instance has been created.
  const std::vector<std::string>& GetValues() { return values_; }

  void WaitForNonEmptyValue() {
    WaitForValue(base::BindRepeating(
        [](const std::string& value) { return !value.empty(); }));
  }

  void WaitForValue(const std::string& expected_value) {
    WaitForValue(
        base::BindLambdaForTesting([expected_value](const std::string& value) {
          return value == expected_value;
        }));
  }

 private:
  using ValuePredicate = base::RepeatingCallback<bool(const std::string&)>;

  struct WaitForValueState {
    explicit WaitForValueState(ValuePredicate predicate)
        : predicate(predicate) {}

    ValuePredicate predicate;
    base::RunLoop run_loop;
  };

  void WaitForValue(ValuePredicate predicate) {
    if (!values_.empty() && predicate.Run(values_.back())) {
      return;
    }
    wait_for_value_state_.emplace(predicate);
    wait_for_value_state_->run_loop.Run();
    wait_for_value_state_.reset();
  }

  const raw_ptr<ash::ShillServiceClient::TestInterface>
      shill_service_client_test_;

  const std::string service_path_;
  const std::string property_name_;

  std::vector<std::string> values_;
  std::optional<WaitForValueState> wait_for_value_state_;
};

// Shorthand for ServicePropertyValueWatcher that allows waiting for a specific
// shill::kStateProperty value.
class ServiceStateWaiter {
 public:
  ServiceStateWaiter(
      ash::ShillServiceClient::TestInterface* shill_service_client_test,
      const std::string& service_path)
      : property_value_watcher_(shill_service_client_test,
                                service_path,
                                shill::kStateProperty) {}

  ServiceStateWaiter(const ServiceStateWaiter&) = delete;
  ServiceStateWaiter& operator=(const ServiceStateWaiter&) = delete;

  void Wait(const std::string& expected_state) {
    property_value_watcher_.WaitForValue(expected_state);
  }

 private:
  ServicePropertyValueWatcher property_value_watcher_;
};

// Waits for `kScanningProperty` of a shill device to be true.
class DeviceScanningWaiter : public ash::ShillPropertyChangedObserver {
 public:
  explicit DeviceScanningWaiter(
      ash::ShillDeviceClient::TestInterface* shill_device_client_test,
      const std::string& device_path)
      : shill_device_client_test_(shill_device_client_test),
        device_path_(device_path) {
    ash::ShillDeviceClient::Get()->AddPropertyChangedObserver(
        dbus::ObjectPath(device_path_), this);

    const base::Value* initial_value =
        shill_device_client_test_->GetDeviceProperty(device_path_,
                                                     shill::kScanningProperty);
    if (initial_value && initial_value->is_bool() && initial_value->GetBool()) {
      waiting_loop_.Quit();
    }
  }

  ~DeviceScanningWaiter() override {
    ash::ShillDeviceClient::Get()->RemovePropertyChangedObserver(
        dbus::ObjectPath(device_path_), this);
  }

  DeviceScanningWaiter(const DeviceScanningWaiter&) = delete;
  DeviceScanningWaiter& operator=(const DeviceScanningWaiter&) = delete;

  void OnPropertyChanged(const std::string& name,
                         const base::Value& value) override {
    if (name != shill::kScanningProperty || !value.is_bool()) {
      return;
    }

    if (value.GetBool()) {
      waiting_loop_.Quit();
    }
  }

  void WaitForScanning() { waiting_loop_.Run(); }

 private:
  const raw_ptr<ash::ShillDeviceClient::TestInterface>
      shill_device_client_test_;

  const std::string device_path_;

  base::RunLoop waiting_loop_;
};

// Observes "ConfigureService" calls to fake shill.
class ConfigureServiceObserver {
 public:
  explicit ConfigureServiceObserver(
      ash::ShillManagerClient::TestInterface* shill_manager_client_test,
      const std::string& service_type)
      : shill_manager_client_test_(shill_manager_client_test),
        service_type_(service_type) {
    shill_manager_client_test_->SetConfigureServiceHook(base::BindRepeating(
        &ConfigureServiceObserver::OnConfigure, base::Unretained(this)));
  }

  ~ConfigureServiceObserver() {
    shill_manager_client_test_->SetConfigureServiceHook({});
  }

  ConfigureServiceObserver(const ConfigureServiceObserver&) = delete;
  ConfigureServiceObserver& operator=(const ConfigureServiceObserver&) = delete;

  void OnConfigure(const base::DictValue& properties) {
    const std::string* type = properties.FindString(shill::kTypeProperty);
    if (type == nullptr) {
      return;
    }
    if (*type != service_type_) {
      return;
    }

    configure_count_++;
  }

  int GetConfigureCount() { return configure_count_; }

 private:
  const raw_ptr<ash::ShillManagerClient::TestInterface>
      shill_manager_client_test_;

  const std::string service_type_;

  int configure_count_ = 0;
};

// Registers itself as ash::NetworkPolicyObserver and records events for
// ash::NetworkPolicyObserver::PoliciesApplied and
// ash::NetworkPolicyObserver::PolicyAppliedtoNetwork.
class ScopedNetworkPolicyApplicationObserver
    : public ash::NetworkPolicyObserver {
 public:
  ScopedNetworkPolicyApplicationObserver() {
    ash::ManagedNetworkConfigurationHandler*
        managed_network_configuration_handler =
            ash::NetworkHandler::Get()->managed_network_configuration_handler();
    managed_network_configuration_handler->AddObserver(this);
  }

  ~ScopedNetworkPolicyApplicationObserver() override {
    ash::ManagedNetworkConfigurationHandler*
        managed_network_configuration_handler =
            ash::NetworkHandler::Get()->managed_network_configuration_handler();
    managed_network_configuration_handler->RemoveObserver(this);
  }

  void PoliciesApplied(const std::string& userhash) override {
    policies_applied_events_.push_back(userhash);
    policies_applied_wait_loop_[userhash].Quit();
  }

  void PolicyAppliedToNetwork(const std::string& service_path) override {
    policy_applied_to_network_events_.push_back(service_path);
  }

  const std::vector<std::string>& policies_applied_events() {
    return policies_applied_events_;
  }
  const std::vector<std::string>& policy_applied_to_network_events() {
    return policy_applied_to_network_events_;
  }

  // Clears all recorded events.
  void ResetEvents() {
    policies_applied_events_.clear();
    policy_applied_to_network_events_.clear();
  }

  void WaitPoliciesApplied(const std::string& userhash) {
    policies_applied_wait_loop_[userhash].Run();
  }

 private:
  std::vector<std::string> policies_applied_events_;
  std::vector<std::string> policy_applied_to_network_events_;

  std::map<std::string, base::RunLoop> policies_applied_wait_loop_;
};

class ScopedNetworkCertLoaderRefreshWaiter
    : public ash::NetworkCertLoader::Observer {
 public:
  ScopedNetworkCertLoaderRefreshWaiter() {
    ash::NetworkCertLoader::Get()->AddObserver(this);
  }

  ~ScopedNetworkCertLoaderRefreshWaiter() override {
    ash::NetworkCertLoader::Get()->RemoveObserver(this);
  }

  void OnCertificatesLoaded() override { run_loop_.Quit(); }

  void Wait() { run_loop_.Run(); }

 private:
  base::RunLoop run_loop_;
};

// Allows waiting until a set of GUIDs is available as NetworkStateProperties
// in CrosNetworkConfig.
class CrosNetworkConfigGuidsAvailableWaiter
    : chromeos::network_config::CrosNetworkConfigObserver {
 public:
  CrosNetworkConfigGuidsAvailableWaiter(
      ash::network_config::CrosNetworkConfig* cros_network_config,
      const std::set<std::string>& expected_guids)
      : cros_network_config_(cros_network_config),
        expected_guids_(expected_guids) {
    // This is a "mojo remote" so it will be automatically removed when
    // destroyed.
    cros_network_config_->AddObserver(
        cros_network_config_observer_receiver_.BindNewPipeAndPassRemote());
  }

  ~CrosNetworkConfigGuidsAvailableWaiter() override = default;

  void Wait() {
    if (DoAllNetworkStatesExist()) {
      return;
    }
    run_loop_.Run();
  }

 private:
  // Fired when the list of networks changes.
  void OnNetworkStateListChanged() override {
    if (DoAllNetworkStatesExist()) {
      run_loop_.Quit();
    }
  }

  bool DoAllNetworkStatesExist() {
    // Use GetNetworkState to check if a NetworkState with `guid_` exists.
    base::test::TestFuture<
        std::vector<network_mojom::NetworkStatePropertiesPtr>>
        network_states_future;
    cros_network_config_->GetNetworkStateList(
        AcceptEverything(), network_states_future.GetCallback());
    std::set<std::string> guids =
        NetworkStatesToGuids(network_states_future.Get());
    return std::includes(guids.begin(), guids.end(), expected_guids_.begin(),
                         expected_guids_.end());
  }

  static network_mojom::NetworkFilterPtr AcceptEverything() {
    return network_mojom::NetworkFilter::New(network_mojom::FilterType::kAll,
                                             network_mojom::NetworkType::kAll,
                                             network_mojom::kNoLimit);
  }

  static std::set<std::string> NetworkStatesToGuids(
      const std::vector<network_mojom::NetworkStatePropertiesPtr>&
          network_states) {
    std::set<std::string> guids;
    std::ranges::transform(network_states, std::inserter(guids, guids.begin()),
                           &network_mojom::NetworkStateProperties::guid);
    return guids;
  }

  base::RunLoop run_loop_;
  const raw_ptr<ash::network_config::CrosNetworkConfig> cros_network_config_;
  const std::set<std::string> expected_guids_;

  // Receiver for the CrosNetworkConfigObserver events.
  mojo::Receiver<network_mojom::CrosNetworkConfigObserver>
      cros_network_config_observer_receiver_{this};
};

std::string OncPolicyToSelectClientCert(const std::string& guid,
                                        const std::string& ssid,
                                        const std::string& issuer_common_name,
                                        const std::string& identity) {
  return base::StringPrintf(R"(
    {
      "NetworkConfigurations": [
        {
          "GUID": "%s",
          "Name": "OncPolicyToSelectClientCert",
          "Type": "WiFi",
          "WiFi": {
             "EAP":  {
              "Outer": "EAP-TLS",
              "ClientCertType": "Pattern",
              "Identity": "%s",
              "ClientCertPattern": {
                "Issuer": {
                  "CommonName": "%s"
                }
              }
             },
             "SSID": "%s",
             "Security": "WPA-EAP"
          }
        }
      ]
    })",
                            guid.c_str(), identity.c_str(),
                            issuer_common_name.c_str(), ssid.c_str());
}

// Returns the configured static IP address from `shill_properties`, or an
// empty string if no static IP address is configured.
std::string GetStaticIPAddressFromShillProperties(
    const base::DictValue& shill_properties) {
  const base::DictValue* static_ip_config =
      shill_properties.FindDict(shill::kStaticIPConfigProperty);
  if (!static_ip_config) {
    return std::string();
  }
  const std::string* address =
      static_ip_config->FindString(shill::kAddressProperty);
  if (!address) {
    return std::string();
  }
  EXPECT_THAT(*address, Not(IsEmpty()));
  return *address;
}

// Returns the configured static name servers from `shill_properties`, or an
// empty vector if no static name servers are configured.
std::vector<std::string> GetStaticNameServersFromShillProperties(
    const base::DictValue& shill_properties) {
  const base::DictValue* static_ip_config =
      shill_properties.FindDict(shill::kStaticIPConfigProperty);
  if (!static_ip_config) {
    return {};
  }
  const base::ListValue* nameservers =
      static_ip_config->FindList(shill::kNameServersProperty);
  if (!nameservers) {
    return {};
  }
  std::vector<std::string> result;
  for (const base::Value& item : *nameservers) {
    result.push_back(item.GetString());
  }
  EXPECT_THAT(result, Not(IsEmpty()));
  return result;
}

// Waits until a views::View exists which is a (possibly indirect) child of a
// parent View and satisfies some predicate.
class ViewWaiter : public views::ViewObserver {
 public:
  // This will be used to check if a view is the one the ViewWaiter is waiting
  // for.
  using ViewPredicate = base::RepeatingCallback<bool(views::View*)>;

  ViewWaiter(views::View* observed_view, ViewPredicate view_predicate)
      : observed_view_(observed_view), view_predicate_(view_predicate) {}

  void Wait() {
    matching_view_ = FindExpectedView(observed_view_);
    if (matching_view_ != nullptr) {
      return;
    }

    scoped_observation_.Observe(observed_view_);
    run_loop.Run();
    if (matching_view_ == nullptr) {
      FAIL() << "Could not find the expected view";
    }
  }

  views::View* GetMatchingView() { return matching_view_; }

 private:
  void OnViewHierarchyChanged(
      views::View* observed_view,
      const views::ViewHierarchyChangedDetails& details) override {
    matching_view_ = FindExpectedView(observed_view_);
    if (matching_view_ != nullptr) {
      run_loop.Quit();
    }
  }

  views::View* FindExpectedView(views::View* view) {
    if (view_predicate_.Run(view)) {
      return view;
    }
    for (views::View* const child : view->children()) {
      if (views::View* const found = FindExpectedView(child)) {
        return found;
      }
    }
    return nullptr;
  }

  raw_ptr<views::View> observed_view_;
  ViewPredicate view_predicate_;
  base::ScopedObservation<views::View, ViewWaiter> scoped_observation_{this};
  base::RunLoop run_loop;
  raw_ptr<views::View> matching_view_;
};

ViewWaiter::ViewPredicate ViewWithLabel(const std::u16string& label) {
  return base::BindLambdaForTesting([label](views::View* view) {
    if (views::Label* const label_view = views::AsViewClass<views::Label>(view);
        label_view && label_view->GetText() == label) {
      return true;
    }
    return false;
  });
}

ViewWaiter::ViewPredicate LabelButtonWithLabel(const std::u16string& label) {
  return base::BindLambdaForTesting([label](views::View* view) {
    if (views::LabelButton* const label_button_view =
            views::AsViewClass<views::LabelButton>(view);
        label_button_view && label_button_view->GetText() == label) {
      return true;
    }
    return false;
  });
}

// Opens the "network detailed view" of the system tray and attempts to press
// on the entry that is displaying `ssid`. This relies on the fact that the
// SSID will be on the corresponding UI label verbatim.
void ConnectToSsidUsingSystemTray(std::string_view ssid) {
  auto system_tray = ash::SystemTrayTestApi::Create();
  system_tray->ShowNetworkDetailedView();

  ViewWaiter waiter(system_tray->GetMainBubbleView(),
                    ViewWithLabel(base::UTF8ToUTF16(ssid)));
  ASSERT_NO_FATAL_FAILURE(waiter.Wait());

  ui::test::EventGenerator event_generator(ash::Shell::GetPrimaryRootWindow());

  event_generator.MoveMouseTo(
      waiter.GetMatchingView()->GetBoundsInScreen().CenterPoint());
  event_generator.ClickLeftButton();
}

void AcceptCertEnrollmentDialog(views::Widget* dialog_widget) {
  ViewWaiter waiter(dialog_widget->GetRootView(),
                    LabelButtonWithLabel(l10n_util::GetStringUTF16(
                        IDS_NETWORK_ENROLLMENT_HANDLER_BUTTON)));
  ASSERT_NO_FATAL_FAILURE(waiter.Wait());

  // For some reason, clicking on the button found by `waiter` (which is the
  // "Accept" button of the dialog) doesn't seem to have an action in a
  // browsertest, so "accept" the dialog programmatically.
  dialog_widget->widget_delegate()->AsDialogDelegate()->AcceptDialog();
}

}  // namespace

// This class is used for implementing integration tests for network policy
// application across sign-in screen and/or user session.
class NetworkPolicyApplicationTest : public ash::LoginManagerTest {
 public:
  NetworkPolicyApplicationTest() : LoginManagerTest() {
    MarkEnterpriseEnrolled();
    login_mixin_.AppendRegularUsers(1);
    test_account_id_ = login_mixin_.users()[0].account_id;
  }

  NetworkPolicyApplicationTest(const NetworkPolicyApplicationTest&) = delete;
  NetworkPolicyApplicationTest& operator=(const NetworkPolicyApplicationTest&) =
      delete;

 protected:
  // InProcessBrowserTest:
  void SetUp() override {
    policy_provider_.SetDefaultReturns(
        /*is_initialization_complete_return=*/true,
        /*is_first_policy_load_complete_return=*/true);
    BrowserPolicyConnector::SetPolicyProviderForTesting(&policy_provider_);

    LoginManagerTest::SetUp();
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    LoginManagerTest::SetUpCommandLine(command_line);

    // Allow policy fetches to fail - these tests use
    // MockConfigurationPolicyProvider.
    command_line->AppendSwitch(ash::switches::kAllowFailedPolicyFetchForTest);
  }

  void SetUpInProcessBrowserTestFixture() override {
    LoginManagerTest::SetUpInProcessBrowserTestFixture();
  }

  void SetUpOnMainThread() override {
    LoginManagerTest::SetUpOnMainThread();

    shill_manager_client_test_ =
        ash::ShillManagerClient::Get()->GetTestInterface();
    shill_service_client_test_ =
        ash::ShillServiceClient::Get()->GetTestInterface();
    shill_profile_client_test_ =
        ash::ShillProfileClient::Get()->GetTestInterface();
    shill_device_client_test_ =
        ash::ShillDeviceClient::Get()->GetTestInterface();
    shill_ip_config_client_test_ =
        ash::ShillIPConfigClient::Get()->GetTestInterface();

    shill_service_client_test_->ClearServices();
    shill_device_client_test_->ClearDevices();
    shill_profile_client_test_->ClearProfiles();

    shill_manager_client_test_->SetWifiServicesVisibleByDefault(false);

    shill_manager_client_test_->AddTechnology(shill::kTypeWifi,
                                              true /* enabled */);
    shill_device_client_test_->AddDevice(kDeviceWifiPath, shill::kTypeWifi,
                                         "stub_wifi_device1");
    shill_profile_client_test_->AddProfile(kSharedProfilePath, "");
    shill_service_client_test_->ClearServices();

    cros_network_config_ =
        std::make_unique<ash::network_config::CrosNetworkConfig>();
  }

  void TearDownOnMainThread() override {
    cros_network_config_.reset();

    shill_manager_client_test_ = nullptr;
    shill_service_client_test_ = nullptr;
    shill_profile_client_test_ = nullptr;
    shill_device_client_test_ = nullptr;
    shill_ip_config_client_test_ = nullptr;

    LoginManagerTest::TearDownOnMainThread();
  }

  void MarkEnterpriseEnrolled() {
    stub_install_attributes_.Get()->SetCloudManaged(kTestDomain, kTestDeviceId);
  }

  // Sets the DeviceEphemeralNetworkPoliciesEnabled policy to `value`.
  void SetDeviceEphemeralNetworkPoliciesEnabledPolicy(bool value) {
    current_policy_.Set(key::kDeviceEphemeralNetworkPoliciesEnabled,
                        POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
                        POLICY_SOURCE_CLOUD, base::Value(value), nullptr);
    policy_provider_.UpdateChromePolicy(current_policy_);
  }

  // Sets `device_onc_policy_blob` as DeviceOpenNetworkConfiguration device
  // policy. If `wait_applied` is true, waits for a
  // NetworkPolicyObserver::PoliciesApplied observer call for the device-wide
  // network profile.
  void SetDeviceOpenNetworkConfiguration(
      const std::string& device_onc_policy_blob,
      bool wait_applied) {
    ScopedNetworkPolicyApplicationObserver network_policy_application_observer;
    current_policy_.Set(key::kDeviceOpenNetworkConfiguration,
                        POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
                        POLICY_SOURCE_CLOUD,
                        base::Value(device_onc_policy_blob), nullptr);
    policy_provider_.UpdateChromePolicy(current_policy_);
    if (wait_applied) {
      network_policy_application_observer.WaitPoliciesApplied(
          /*userhash=*/std::string());
    }
  }

  // Sets `user_onc_policy_blob` as OpenNetworkConfiguration user policy using
  // `policy_provider_`. If `wait_applied` is true, waits for a
  // NetworkPolicyObserver::PoliciesApplied observer call for the network
  // profile for `user_hash`.
  void SetUserOpenNetworkConfiguration(const std::string& user_hash,
                                       const std::string& user_onc_policy_blob,
                                       bool wait_applied) {
    ScopedNetworkPolicyApplicationObserver network_policy_application_observer;
    current_policy_.Set(key::kOpenNetworkConfiguration, POLICY_LEVEL_MANDATORY,
                        POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
                        base::Value(user_onc_policy_blob), nullptr);
    policy_provider_.UpdateChromePolicy(current_policy_);
    if (wait_applied) {
      network_policy_application_observer.WaitPoliciesApplied(user_hash);
    }
  }

  void OnConnectToServiceFailed(base::OnceClosure run_loop_quit_closure,
                                const std::string& error_name,
                                const std::string& error_message) {
    ADD_FAILURE() << "Connect failed with " << error_name << " - "
                  << error_message;
    std::move(run_loop_quit_closure).Run();
  }

  void ConnectToService(const std::string& service_path) {
    base::RunLoop run_loop;
    ash::ShillServiceClient::Get()->Connect(
        dbus::ObjectPath(service_path), run_loop.QuitClosure(),
        base::BindOnce(&NetworkPolicyApplicationTest::OnConnectToServiceFailed,
                       base::Unretained(this), run_loop.QuitClosure()));
    run_loop.Run();
  }

  // Connect to `service_path` as if a user action triggered the connection.
  base::expected<void, std::string> ManualConnect(
      const std::string& service_path) {
    base::test::TestFuture<base::expected<void, std::string>> result;

    auto success_callback = base::BindLambdaForTesting(
        [&result]() { result.SetValue(base::ok()); });

    auto failure_callback =
        base::BindLambdaForTesting([&result](const std::string& error) {
          result.SetValue(base::unexpected(error));
        });

    ash::NetworkHandler::Get()->network_connection_handler()->ConnectToNetwork(
        service_path, success_callback, failure_callback,
        /*check_error_state=*/false, ash::ConnectCallbackMode::ON_COMPLETED);

    return result.Get();
  }

  // Imports the certificate and key described by the |cert_filename| and
  // |key_filename| files in |source_dir| to the system token (device-wide).
  // Then triggers NetworkCertLoader to re-load its certificates cache.
  // Should be wrapped in ASSERT_NO_FATAL_FAILURE.
  void ImportCert(const base::FilePath& source_dir,
                  const std::string& cert_filename,
                  const std::string& key_filename) {
    // Before importing, configure NetworkCertLoader to assume that all
    // certificates can be used for network authentication.
    ash::NetworkCertLoader::Get()->ForceAvailableForNetworkAuthForTesting();

    net::ScopedCERTCertificate cert;
    // Import testing key pair and certificate.
    {
      base::ScopedAllowBlockingForTesting allow_io;
      net::ImportClientCertAndKeyFromFile(
          source_dir, cert_filename, key_filename,
          system_nss_key_slot_mixin_.slot(), &cert);
    }
    ASSERT_TRUE(cert);

    // Trigger refreshing the NetworkCertLoader's cache so the certificate
    // becomes available for networks. Production code does this through
    // NSSCertDatabase::ImportUserCert.
    ScopedNetworkCertLoaderRefreshWaiter network_cert_loader_refresh_waiter;
    net::CertDatabase::GetInstance()->NotifyObserversClientCertStoreChanged();
    network_cert_loader_refresh_waiter.Wait();
  }

  // Applies `properties` to the network identified by `guid` using
  // CrosNetworkConfig.
  void CrosNetworkConfigSetProperties(
      const std::string& guid,
      network_mojom::ConfigPropertiesPtr properties) {
    base::test::TestFuture<bool, std::string> set_properties_future;
    cros_network_config_->SetProperties(
        guid, std::move(properties),
        set_properties_future.GetCallback<bool, const std::string&>());
    ASSERT_TRUE(set_properties_future.Wait());
    ASSERT_TRUE(set_properties_future.Get<bool>())
        << "Error msg: " << set_properties_future.Get<std::string>();
  }

  // Retrieves the "managed properties" of the network identified by `guid`
  // using CrosNetworkConfig.
  network_mojom::ManagedPropertiesPtr CrosNetworkConfigGetManagedProperties(
      const std::string& guid) {
    base::test::TestFuture<network_mojom::ManagedPropertiesPtr>
        get_managed_properties_future;
    cros_network_config_->GetManagedProperties(
        guid, get_managed_properties_future.GetCallback());
    return get_managed_properties_future.Take();
  }

  network_mojom::GlobalPolicyPtr CrosNetworkConfigGetGlobalPolicy() {
    base::test::TestFuture<network_mojom::GlobalPolicyPtr> global_policy_ptr;
    cros_network_config_->GetGlobalPolicy(global_policy_ptr.GetCallback());
    return global_policy_ptr.Take();
  }

  network_mojom::NetworkStatePropertiesPtr
  CrosNetworkConfigGetNetworkStateProps(const std::string& guid) {
    base::test::TestFuture<network_mojom::NetworkStatePropertiesPtr>
        network_state_props_ptr;
    cros_network_config_->GetNetworkState(
        guid, network_state_props_ptr.GetCallback());
    return network_state_props_ptr.Take();
  }

  network_mojom::NetworkCertificatePtr CrosNetworkConfigFindClientCert(
      const std::string& ui_name) {
    ash::network_config::CrosNetworkConfig cros_network_config;
    base::test::TestFuture<std::vector<network_mojom::NetworkCertificatePtr>,
                           std::vector<network_mojom::NetworkCertificatePtr>>
        future;
    cros_network_config.GetNetworkCertificates(future.GetCallback());
    std::vector<network_mojom::NetworkCertificatePtr> client_certs =
        std::get<1>(future.Take());
    for (auto& client_cert : client_certs) {
      if (client_cert->issued_to == ui_name) {
        return std::move(client_cert);
      }
    }
    return {};
  }

  // Extracts the UIData dictionary from the shill UIData property of the
  // service `service_path`.
  std::optional<base::DictValue> GetUIDataDict(
      const std::string& service_path) {
    const base::DictValue* properties =
        shill_service_client_test_->GetServiceProperties(service_path);
    if (!properties)
      return {};
    const std::string* ui_data_json =
        properties->FindString(shill::kUIDataProperty);
    if (!ui_data_json)
      return {};
    std::optional<base::DictValue> ui_data_value = base::JSONReader::ReadDict(
        *ui_data_json, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
    if (!ui_data_value) {
      return {};
    }
    return std::move(*ui_data_value);
  }

  // Sets the shill UIData property of the service `service_path` to the
  // serialized `ui_data_dict`.
  void SetUIDataDict(const std::string& service_path,
                     const base::DictValue& ui_data_dict) {
    std::string ui_data_json = base::WriteJson(ui_data_dict).value_or("");
    shill_service_client_test_->SetServiceProperty(
        service_path, shill::kUIDataProperty, base::Value(ui_data_json));
  }

  base::DictValue CreateUIDataWithIPAddr(const std::string& ip_addr) {
    base::DictValue onc_static_ip_config;
    onc_static_ip_config.Set(::onc::ipconfig::kIPAddress, ip_addr);
    onc_static_ip_config.Set(::onc::ipconfig::kType, ::onc::ipconfig::kIPv4);

    base::DictValue user_settings;
    user_settings.Set(::onc::network_config::kIPAddressConfigType,
                      ::onc::network_config::kIPConfigTypeStatic);
    user_settings.Set(::onc::network_config::kStaticIPConfig,
                      std::move(onc_static_ip_config));

    base::DictValue ui_data;
    ui_data.Set("user_settings", std::move(user_settings));

    return ui_data;
  }

  // Returns the GUID from the "user_settings" from `ui_data` or an empty string
  // of no "user_settings" or no GUID was found.
  std::string GetGUIDFromUIData(const base::DictValue& ui_data) {
    const base::DictValue* user_settings =
        ui_data.FindDict(kUIDataKeyUserSettings);
    if (!user_settings)
      return std::string();
    const std::string* guid =
        user_settings->FindString(::onc::network_config::kGUID);
    if (!guid)
      return std::string();
    return *guid;
  }

  const base::DictValue* GetWifiProps(const std::string& guid) {
    std::optional<std::string> wifi_service;
    wifi_service = shill_service_client_test_->FindServiceMatchingGUID(guid);
    if (wifi_service->empty()) {
      ADD_FAILURE() << "No wifi service found for: " << guid;
    }
    return shill_service_client_test_->GetServiceProperties(
        wifi_service.value());
  }

  void SetServiceVisibility(const std::string& guid, bool visible) {
    std::optional<std::string> wifi_service;
    wifi_service = shill_service_client_test_->FindServiceMatchingGUID(guid);
    if (wifi_service->empty()) {
      ADD_FAILURE() << "No wifi service found for: " << guid;
      return;
    }
    shill_service_client_test_->SetServiceProperty(
        wifi_service.value(), shill::kVisibleProperty, base::Value(visible));
  }

  const std::string GetTestUserHash() {
    return user_manager::UserManager::Get()
        ->FindUser(test_account_id_)
        ->username_hash();
  }

  bool IsProhibitedByPolicyInCrosNetworkConfig(const std::string& guid) {
    return CrosNetworkConfigGetNetworkStateProps(guid)->prohibited_by_policy;
  }

  const std::string GetWifiStateFromShillClient(const std::string& guid) {
    const base::DictValue* wifi_properties = GetWifiProps(guid);
    const std::string* wifi_state =
        wifi_properties->FindString(shill::kStateProperty);
    if (!wifi_state) {
      ADD_FAILURE() << "Network has no WiFi state properties: " << guid;
      return "";
    }
    return *wifi_state;
  }

  void AddPskWifiService(const std::string& service_path,
                         const std::string& initial_guid,
                         const std::string& ssid,
                         const std::string& initial_state) {
    shill_service_client_test_->AddService(service_path, initial_guid, ssid,
                                           shill::kTypeWifi, initial_state,
                                           /*visible=*/true);
    shill_service_client_test_->SetServiceProperty(
        service_path, shill::kSSIDProperty, base::Value(ssid));
    shill_service_client_test_->SetServiceProperty(
        service_path, shill::kSecurityClassProperty,
        base::Value(shill::kSecurityClassPsk));
  }

  void Add8021xWifiService(const std::string& service_path,
                           const std::string& initial_guid,
                           const std::string& ssid,
                           const std::string& initial_state) {
    shill_service_client_test_->AddService(service_path, initial_guid, ssid,
                                           shill::kTypeWifi, initial_state,
                                           /*visible=*/true);
    shill_service_client_test_->SetServiceProperty(
        service_path, shill::kSSIDProperty, base::Value(ssid));
    shill_service_client_test_->SetServiceProperty(
        service_path, shill::kSecurityClassProperty,
        base::Value(shill::kSecurityClass8021x));
  }

  // Adds a pre-existing PEAP-MSCHAPv2 service into the shared profile that is
  // marked as originating from device policy.
  void AddSharedDevicePolicyMschapv2Service(
      const std::string& service_path,
      const std::string& initial_guid,
      const std::string& ssid,
      const std::string& initial_identity) {
    Add8021xWifiService(service_path, initial_guid, "wifi1", shill::kStateIdle);
    shill_profile_client_test_->AddService(kSharedProfilePath, service_path);
    shill_service_client_test_->SetServiceProperty(
        service_path, shill::kEapMethodProperty, base::Value("PEAP"));
    shill_service_client_test_->SetServiceProperty(
        service_path, shill::kEapIdentityProperty,
        base::Value(initial_identity));
    shill_service_client_test_->SetServiceProperty(
        service_path, shill::kUIDataProperty,
        base::Value(R"({"onc_source":"device_policy"})"));
  }

  void SimulateFullWifiScan() {
    DeviceScanningWaiter waiter(shill_device_client_test_, kDeviceWifiPath);

    shill_device_client_test_->SetDeviceProperty(
        kDeviceWifiPath, shill::kScanningProperty, base::Value(true),
        /*notify_changed=*/true);

    waiter.WaitForScanning();

    SimulateWifiScanCompleted();
  }

  void SimulateWifiScanCompleted() {
    shill_manager_client_test_->TriggerScanCompleted(kDeviceWifiPath);
  }

  // Unowned pointers -- just pointers to the singleton instances.
  raw_ptr<ash::ShillManagerClient::TestInterface> shill_manager_client_test_ =
      nullptr;
  raw_ptr<ash::ShillServiceClient::TestInterface> shill_service_client_test_ =
      nullptr;
  raw_ptr<ash::ShillProfileClient::TestInterface> shill_profile_client_test_ =
      nullptr;
  raw_ptr<ash::ShillDeviceClient::TestInterface> shill_device_client_test_ =
      nullptr;
  raw_ptr<ash::ShillIPConfigClient::TestInterface>
      shill_ip_config_client_test_ = nullptr;

  ash::ScopedStubInstallAttributes stub_install_attributes_;

  AccountId test_account_id_;

  std::unique_ptr<ash::network_config::CrosNetworkConfig> cros_network_config_;

 private:
  ash::ScopedTestSystemNSSKeySlotMixin system_nss_key_slot_mixin_{&mixin_host_};
  ash::LoginManagerMixin login_mixin_{&mixin_host_};

  testing::NiceMock<MockConfigurationPolicyProvider> policy_provider_;
  PolicyMap current_policy_;
};

// A variant of NetworkPolicyApplicationTest which enables the
// EphemeralNetworkPolicies feature.
class NetworkPolicyApplicationEphemeralActionsEnabledTest
    : public NetworkPolicyApplicationTest {
 public:
  NetworkPolicyApplicationEphemeralActionsEnabledTest() {
    scoped_feature_list_.InitAndEnableFeature(
        ash::features::kEphemeralNetworkPolicies);
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

// A variant of NetworkPolicyApplicationTest which disables the
// EphemeralNetworkPolicies feature.
class NetworkPolicyApplicationEphemeralActionsDisabledTest
    : public NetworkPolicyApplicationTest {
 public:
  NetworkPolicyApplicationEphemeralActionsDisabledTest() {
    scoped_feature_list_.InitWithFeatures(
        /*enabled_features=*/{},
        /*disabled_features=*/{ash::features::kEphemeralNetworkPolicies});
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

// A variant of NetworkPolicyApplicationTest which enables the
// EphemeralNetworkPolicies feature and the device is not enterprise enrolled at
// startup.
class NetworkPolicyApplicationEphemeralActionsEnabledUnenrolledTest
    : public NetworkPolicyApplicationTest {
 public:
  NetworkPolicyApplicationEphemeralActionsEnabledUnenrolledTest() {
    scoped_feature_list_.InitAndEnableFeature(
        ash::features::kEphemeralNetworkPolicies);
    stub_install_attributes_.Get()->Clear();
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

// A variant of NetworkPolicyApplicationTest which activates the "kill switch"
// for the eth policy re-application fix (b/467741000) by disabling the
// corresponding feature.
class NetworkPolicyApplicationNoEthFixTest
    : public NetworkPolicyApplicationTest {
 public:
  NetworkPolicyApplicationNoEthFixTest() {
    scoped_feature_list_.InitAndDisableFeature(
        ash::features::kFixStaticIpForTwoManagedEthPorts);
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

// This test applies a global network policy with
// AllowOnlyPolicyNetworksToAutoconnect set to true. It then performs a user
// log-in and simulates that user policy application is slow. This is a
// regression test for https://crbug.com/41444046.
// Specifically, it simulates that:
// 1. ash-chrome applies device network policy in shill.
//    The device policy mandates that only policy configured networks may
//    auto-connect.
// 2. The user manually connects to a non-policy-managed network
// 3. The user signs in and ash-chrome applies user network policy in shill.
//    Important:
//    shill does not reflect the property changes back to chrome through
//    D-Bus PropertyChanged events yet.
//    In the test, this is simulated by
//      shill_service_client_test_->SetHoldBackServicePropertyUpdates(true);
// In this case, the signal that policies have been applied yet may not be
// triggered yet.
// Only after shill is allowed to send PropertyChanged events to chrome will
// chrome's data models be updated, and then the "policies applied" signal
// should be triggered.
//
// This is checked in the test in two ways:
// - Direct observation of ash::NetworkPolicyObserver through
// ScopedNetworkPolicyApplicationObserver.
// - Checking that AutoConnectHandler didn't disconnect the manually-connected
//   network, which was an observable consequence of the bug in this setup.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
                       OnlyPolicyAutoconnectWithSlowUserPolicyApplication) {
  ScopedNetworkPolicyApplicationObserver network_policy_application_observer;

  // Set up two services.
  AddPskWifiService(kServiceWifi1, "wifi_orig_guid_1", "WifiOne",
                    shill::kStateIdle);
  AddPskWifiService(kServiceWifi2, "wifi_orig_guid_2", "WifiTwo",
                    shill::kStateIdle);

  // Apply device ONC policy and wait until it takes effect (one of the networks
  // auto connects).
  const char kDeviceONC[] = R"(
    {
      "GlobalNetworkConfiguration": {
        "AllowOnlyPolicyNetworksToAutoconnect": true,
        "AllowOnlyPolicyNetworksToConnect": false
      },
      "NetworkConfigurations": [
        {
          "GUID": "{device-policy-for-Wifi1}",
          "Name": "DeviceLevelWifi",
          "Type": "WiFi",
          "WiFi": {
             "AutoConnect": true,
             "HiddenSSID": false,
             "Passphrase": "DeviceLevelWifiPwd",
             "SSID": "WifiOne",
             "Security": "WPA-PSK"
          }
        }
      ]
    })";
  ServiceStateWaiter wifi_one_connected_waiter(shill_service_client_test_,
                                               kServiceWifi1);
  shill_manager_client_test_->SetBestServiceToConnect(kServiceWifi1);
  SetDeviceOpenNetworkConfiguration(kDeviceONC, /*wait_applied=*/true);
  wifi_one_connected_waiter.Wait(shill::kStateOnline);

  EXPECT_THAT(
      network_policy_application_observer.policy_applied_to_network_events(),
      ElementsAre(kServiceWifi1));
  EXPECT_THAT(network_policy_application_observer.policies_applied_events(),
              ElementsAre(std::string() /* shill shared profile */));
  network_policy_application_observer.ResetEvents();

  std::optional<std::string> wifi_service =
      shill_service_client_test_->FindServiceMatchingGUID(
          "{device-policy-for-Wifi1}");
  ASSERT_TRUE(wifi_service);
  EXPECT_THAT(
      shill_service_client_test_->GetServiceProperties(wifi_service.value()),
      Pointee(DictionaryHasValues(
          base::DictValue()
              .Set(shill::kAutoConnectProperty, true)
              .Set(shill::kProfileProperty, kSharedProfilePath))));

  // Manually connect to the other network.
  ConnectToService(kServiceWifi2);

  // Sign-in a user and apply user ONC policy. Simulate that shill takes a while
  // to reflect the changes back to chrome by holding back service property
  // updates (regression test for https://crbug.com/41444046).
  shill_service_client_test_->SetHoldBackServicePropertyUpdates(true);

  LoginUser(test_account_id_);
  const std::string user_hash = user_manager::UserManager::Get()
                                    ->FindUser(test_account_id_)
                                    ->username_hash();
  shill_profile_client_test_->AddProfile(kUserProfilePath, user_hash);

  // When AutoConnectHandler triggers ScanAndConnectToBestServices, shill should
  // not do anything for now. This allows us to test whether AutoConnectHandler
  // is explicitly disconnecting networks.
  shill_manager_client_test_->SetBestServiceToConnect(std::string());
  const char kUserONC[] = R"(
    {
      "NetworkConfigurations": [
        {
          "GUID": "{user-policy-for-Wifi1}",
          "Name": "DeviceLevelWifi",
          "Type": "WiFi",
          "WiFi": {
             "AutoConnect": false,
             "HiddenSSID": false,
             "Passphrase": "DeviceLevelWifiPwd",
             "SSID": "WifiOne",
             "Security": "WPA-PSK"
          }
        },
        {
          "GUID": "{user-policy-for-Wifi2}",
          "Name": "UserLevelWifi",
          "Type": "WiFi",
          "WiFi": {
             "AutoConnect": true,
             "HiddenSSID": false,
             "Passphrase": "UserLevelWifiPwd",
             "SSID": "WifiTwo",
             "Security": "WPA-PSK"
          }
        }
      ]
    })";
  SetUserOpenNetworkConfiguration(user_hash, kUserONC, /*wait_applied=*/false);

  // Expect that the policies have not been signalled as applied yet because
  // property updates are being held back by FakeShillServiceClient.
  EXPECT_TRUE(
      network_policy_application_observer.policy_applied_to_network_events()
          .empty());
  EXPECT_TRUE(
      network_policy_application_observer.policies_applied_events().empty());

  // Now let fake shill reflect the property updates, so policy application is
  // marked as done.
  shill_service_client_test_->SetHoldBackServicePropertyUpdates(false);
  network_policy_application_observer.WaitPoliciesApplied(user_hash);
  EXPECT_THAT(
      network_policy_application_observer.policy_applied_to_network_events(),
      ElementsAre(kServiceWifi1, kServiceWifi2));

  // Expect that the same service path now has the user policy GUID.
  EXPECT_THAT(
      shill_service_client_test_->GetServiceProperties(wifi_service.value()),
      Pointee(DictionaryHasValues(
          base::DictValue()
              .Set(shill::kGuidProperty, "{user-policy-for-Wifi1}")
              .Set(shill::kAutoConnectProperty, false)
              .Set(shill::kProfileProperty, kUserProfilePath)
              .Set(shill::kStateProperty, shill::kStateIdle))));

  std::optional<std::string> wifi2_service =
      shill_service_client_test_->FindServiceMatchingGUID(
          "{user-policy-for-Wifi2}");
  ASSERT_TRUE(wifi2_service);
  // This service is still connected. This is an important check in this
  // regression test:
  // In https://crbug.com/41444046, AutoConnectHandler was already running
  // (because OnPoliciesApplied was already triggered) when the NetworkState
  // for a policy-managed network was not marked managed yet (because shill
  // has not reflected the property changes yet). As a consequence,
  // AutoConnectHandler disconnected the current network because of the global
  // AllowOnlyPolicyNetworksToAutoconnect policy. Verify that this has not
  // happened in this test.
  EXPECT_THAT(
      shill_service_client_test_->GetServiceProperties(wifi2_service.value()),
      Pointee(DictionaryHasValues(
          base::DictValue()
              .Set(shill::kAutoConnectProperty, true)
              .Set(shill::kStateProperty, shill::kStateOnline))));
}

// Verify that AllowOnlyPolicyNetworksToConnect is working correctly , so
// non-policy-managed networks can be connected before user's login, but
// they will be automatically disconnected after the user's login and they also
// will be forbidden in CrosNetworkConfig.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
                       OnlyPolicyAllowedNetworkCanBeConected) {
  ScopedNetworkPolicyApplicationObserver policy_observer;
  ash::ShillServiceClient::TestInterface* shill_client =
      shill_service_client_test_;
  constexpr char kGuidWifi1[] = "wifi_orig_guid_1";
  constexpr char kGuidWifi2[] = "wifi_orig_guid_2";
  constexpr char kGuidWifi3[] = "wifi_orig_guid_3";
  constexpr char kGuidWifi4[] = "wifi_orig_guid_4";

  // Network 1 will be managed by device policy and it will have auto connect.
  // Network 2 will be managed by user policy.
  // Network 3 and 4 will be not managed. Network 4 is added just to make sure
  // that we have more than one un-managed network.
  CrosNetworkConfigGuidsAvailableWaiter available_waiter(
      cros_network_config_.get(),
      {kGuidWifi1, kGuidWifi2, kGuidWifi3, kGuidWifi4});
  AddPskWifiService(kServiceWifi1, kGuidWifi1, "WifiOne", shill::kStateIdle);
  AddPskWifiService(kServiceWifi2, kGuidWifi2, "WifiTwo", shill::kStateIdle);
  AddPskWifiService(kServiceWifi3, kGuidWifi3, "WifiThree", shill::kStateIdle);
  AddPskWifiService(kServiceWifi4, kGuidWifi4, "WifiFour", shill::kStateIdle);
  available_waiter.Wait();

  // Check that initially no policies applied and CrosNetworkStateProperties
  // has no prohibited networks.
  {
    EXPECT_FALSE(CrosNetworkConfigGetGlobalPolicy()
                     ->allow_only_policy_wifi_networks_to_connect);

    EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
    EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
    EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi3));
    EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi4));
  }

  // Apply device ONC policy and wait until it takes effect (1st network
  // start auto connects). Those setting are not effective before user's login,
  // but we can check that CrosNetworkStateProperties are updated correctly.
  {
    const char kDeviceONC[] = R"(
        {
          "GlobalNetworkConfiguration": {
            "AllowOnlyPolicyNetworksToConnect": true
          },
          "NetworkConfigurations": [
            {
              "GUID": "wifi_orig_guid_1",
              "Name": "DeviceLevelWifi",
              "Type": "WiFi",
              "WiFi": {
                "AutoConnect": true,
                "HiddenSSID": false,
                "Passphrase": "DeviceLevelWifiPwd",
                "SSID": "WifiOne",
                "Security": "WPA-PSK"
              }
            }
          ]
        })";

    ServiceStateWaiter wifi_one_connected_waiter(shill_client, kServiceWifi1);
    shill_manager_client_test_->SetBestServiceToConnect(kServiceWifi1);
    SetDeviceOpenNetworkConfiguration(kDeviceONC, /*wait_applied=*/true);
    wifi_one_connected_waiter.Wait(shill::kStateOnline);
  }

  // Check before login that device can connect to any available network.
  // This is because AllowOnlyPolicyNetworksToConnect only applies in user's
  // sessions, even though it is a device-wide network policy.
  // WiFi3 will be kept online and it should be disconnected automatically after
  // the login because policy will take action, WiFi1 will become connected
  // after the login because it has "autoconnect".
  {
    // Verify that GlobalPolicy from CrosNetworkConfig will allow connection
    // only to networks defined and all networks are not prohibited.
    EXPECT_TRUE(CrosNetworkConfigGetGlobalPolicy()
                    ->allow_only_policy_wifi_networks_to_connect);

    EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
    EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
    EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi3));
    EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi4));

    // Check that device is connected to WiFi1 while other networks (2,3,4)
    // are "idle".
    EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateOnline);
    EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateIdle);
    EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi3), shill::kStateIdle);
    EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi4), shill::kStateIdle);

    // Manually connect to the 2nd network and verify that it is "online"
    // while network 1, 3, 4 are "idle".
    ConnectToService(kServiceWifi2);
    EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateIdle);
    EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateOnline);
    EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi3), shill::kStateIdle);
    EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi4), shill::kStateIdle);

    // Manually connect to the 3d network and verify that it is "online"
    // network 1, 2, 4 are "idle".
    ConnectToService(kServiceWifi3);
    EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateIdle);
    EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateIdle);
    EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi3), shill::kStateOnline);
    EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi4), shill::kStateIdle);
  }

  // Sign-in a user and apply user ONC policy for WiFi2.
  {
    LoginUser(test_account_id_);
    const std::string user_hash = GetTestUserHash();
    shill_profile_client_test_->AddProfile(kUserProfilePath, user_hash);

    const char kUserONC[] = R"(
        {
          "NetworkConfigurations": [
            {
              "GUID": "wifi_orig_guid_2",
              "Name": "UserLevelWifi",
              "Type": "WiFi",
              "WiFi": {
                "AutoConnect": true,
                "HiddenSSID": false,
                "Passphrase": "UserLevelWifiPwd",
                "SSID": "WifiTwo",
                "Security": "WPA-PSK"
              }
            }
          ]
        })";

    SetUserOpenNetworkConfiguration(user_hash, kUserONC,
                                    /*wait_applied=*/true);
  }

  // Verify that WiFi3 is disconnected as it is not included into device or
  // into user's policies. Also now WiFi3 and WiFi4 are prohibited
  // by policy. WiFi1 should be connected (online) because it has auto connect.
  {
    EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateOnline);
    EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateIdle);
    EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi3), shill::kStateIdle);
    EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi4), shill::kStateIdle);

    EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
    EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
    EXPECT_TRUE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi3));
    EXPECT_TRUE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi4));
  }
}

// Tests behavior of BlockedHexSSIDs on login screen and in a user session.
// Also tests that if a blocked SSID was connected on the sign-in screen, it is
// disconnected when a user signs in.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest, BlockedHexSSIDs) {
  constexpr char kGuidWifi1[] = "wifi_orig_guid_1";
  constexpr char kGuidWifi2[] = "wifi_orig_guid_2";

  CrosNetworkConfigGuidsAvailableWaiter available_waiter(
      cros_network_config_.get(), {kGuidWifi1, kGuidWifi2});
  AddPskWifiService(kServiceWifi1, kGuidWifi1, "WifiOne", shill::kStateIdle);
  AddPskWifiService(kServiceWifi2, kGuidWifi2, "WifiTwo", shill::kStateIdle);
  available_waiter.Wait();

  // Check that initially no policies applied and CrosNetworkStateProperties
  // has no prohibited networks.
  {
    EXPECT_THAT(CrosNetworkConfigGetGlobalPolicy()->blocked_hex_ssids,
                IsEmpty());

    EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
    EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
  }

  // Apply device ONC policy.
  // 576966694F6E65 is hex-encoded ASCII "WifiOne"
  {
    const char kDeviceONC[] = R"(
        {
          "GlobalNetworkConfiguration": {
            "BlockedHexSSIDs": [ "576966694F6E65" ]
          },
          "NetworkConfigurations": [ ]
        })";
    SetDeviceOpenNetworkConfiguration(kDeviceONC, /*wait_applied=*/true);

    EXPECT_THAT(CrosNetworkConfigGetGlobalPolicy()->blocked_hex_ssids,
                ElementsAre("576966694F6E65"));
  }

  // The network is still connectable because BlockedHexSSIDs is not applied on
  // the sign-in screen.
  EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
  EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));

  ConnectToService(kServiceWifi1);
  EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateOnline);
  EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateIdle);

  // Sign-in a user and apply user without ONC policy.
  // The blocked network should be automatically disconnected.
  {
    ServiceStateWaiter wifi_disconnected_waiter(shill_service_client_test_,
                                                kServiceWifi1);

    LoginUser(test_account_id_);
    const std::string user_hash = GetTestUserHash();
    shill_profile_client_test_->AddProfile(kUserProfilePath, user_hash);

    wifi_disconnected_waiter.Wait(shill::kStateIdle);
  }

  EXPECT_TRUE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
  EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));

  EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateIdle);
  EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateIdle);

  // TODO(b/277809215): Attempt to connect to the prohibited service using
  // CrosNetworkConfig.
}

// Behavior of AllowOnlyPolicyNetworksToConnectIfAvailable when no policy
// networks are configured.
// This variant tests that after the wifi scan after user login, no automatic
// change happens.
IN_PROC_BROWSER_TEST_F(
    NetworkPolicyApplicationTest,
    OnlyPolicyIfAvailable_NoPolicyNetwork_NoChangeAfterScan) {
  constexpr char kGuidWifi1[] = "wifi_orig_guid_1";
  constexpr char kGuidWifi2[] = "wifi_orig_guid_2";

  CrosNetworkConfigGuidsAvailableWaiter available_waiter(
      cros_network_config_.get(), {kGuidWifi1, kGuidWifi2});
  AddPskWifiService(kServiceWifi1, kGuidWifi1, "WifiOne", shill::kStateIdle);
  AddPskWifiService(kServiceWifi2, kGuidWifi2, "WifiTwo", shill::kStateIdle);
  available_waiter.Wait();

  // Check that initially no policies applied and CrosNetworkStateProperties
  // has no prohibited networks.
  {
    EXPECT_FALSE(CrosNetworkConfigGetGlobalPolicy()
                     ->allow_only_policy_wifi_networks_to_connect);

    EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
    EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
  }

  // Apply device ONC policy
  {
    const char kDeviceONC[] = R"(
        {
          "GlobalNetworkConfiguration": {
            "AllowOnlyPolicyNetworksToConnectIfAvailable": true
          },
          "NetworkConfigurations": [ ]
        })";
    SetDeviceOpenNetworkConfiguration(kDeviceONC, /*wait_applied=*/true);

    EXPECT_TRUE(CrosNetworkConfigGetGlobalPolicy()
                    ->allow_only_policy_wifi_networks_to_connect_if_available);
  }

  // Manually connecting to a non-policy network must be possible.
  EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
  EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));

  ConnectToService(kServiceWifi1);
  EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateOnline);
  EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateIdle);

  // FakeShillManagerClient should not auto-reset the shill::kScanningProperty,
  // so the test can simulate a state where a scan has not finished yet.
  shill_manager_client_test_->SetAutoCompleteScan(false);

  // Sign-in a user without ONC policy.
  {
    ScopedNetworkPolicyApplicationObserver network_policy_application_observer;

    LoginUser(test_account_id_);
    const std::string user_hash = GetTestUserHash();
    shill_profile_client_test_->AddProfile(kUserProfilePath, user_hash);

    // Even though the user doesn't have user ONC policy, an empty user policy
    // application will be signaled.
    network_policy_application_observer.WaitPoliciesApplied(user_hash);
  }

  // Wait for scanning to start (triggered by user login).
  DeviceScanningWaiter(shill_device_client_test_, kDeviceWifiPath)
      .WaitForScanning();

  // Manual connection attempts are blocked during scanning due to
  // AllowOnlyPolicyNetworksToConnectIfAvailable (https://crbug.com/442695977).
  // In theory this would not be needed because no policy network is actually
  // configured, but that's the current implementation. The policy configuration
  // of "no policy network configured but
  // AllowOnlyPolicyNetworksToConnectIfAvailable" is a bit strange anyway, so it
  // seemed like unnecessary complexity to special case this case.
  {
    const base::expected<void, std::string> manual_connect_result =
        ManualConnect(kServiceWifi2);
    EXPECT_FALSE(manual_connect_result.has_value())
        << "Expected manual connection attempt to be blocked until scan "
           "finished.";
    EXPECT_EQ(manual_connect_result.error(),
              ash::NetworkConnectionHandler::kErrorWaitingForScan);
  }

  // Let scanning finish.
  SimulateWifiScanCompleted();

  // WiFi1 should still be connected, no networks are prohibited.
  EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
  EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));

  EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateOnline);
  EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateIdle);
}

// Behavior of AllowOnlyPolicyNetworksToConnectIfAvailable when no policy
// networks are visible initially, then after user login, a policy-provided
// network becomes visible. After that the policy-provided network becomes not
// visible again.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
                       OnlyPolicyIfAvailable_VisibleBackAndForth) {
  constexpr char kGuidWifi1[] = "wifi_orig_guid_1";
  constexpr char kGuidWifi2[] = "wifi_orig_guid_2";

  CrosNetworkConfigGuidsAvailableWaiter available_waiter(
      cros_network_config_.get(), {kGuidWifi1, kGuidWifi2});
  AddPskWifiService(kServiceWifi1, kGuidWifi1, "WifiOne", shill::kStateIdle);
  AddPskWifiService(kServiceWifi2, kGuidWifi2, "WifiTwo", shill::kStateIdle);
  available_waiter.Wait();

  // Check that initially no policies applied and CrosNetworkStateProperties
  // has no prohibited networks.
  {
    EXPECT_FALSE(CrosNetworkConfigGetGlobalPolicy()
                     ->allow_only_policy_wifi_networks_to_connect);

    EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
    EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
  }

  // Apply device ONC policy
  {
    const char kDeviceONC[] = R"(
        {
          "GlobalNetworkConfiguration": {
            "AllowOnlyPolicyNetworksToConnectIfAvailable": true
          },
          "NetworkConfigurations": [
            {
              "GUID": "wifi_policy_1",
              "Name": "PolicyDeviceLevelWifi",
              "Type": "WiFi",
              "WiFi": {
                "HiddenSSID": false,
                "Passphrase": "DeviceLevelWifiPwd",
                "SSID": "PolicyDeviceLevelWifi",
                "Security": "WPA-PSK"
              }
            }
          ]
        })";
    SetDeviceOpenNetworkConfiguration(kDeviceONC, /*wait_applied=*/true);

    EXPECT_TRUE(CrosNetworkConfigGetGlobalPolicy()
                    ->allow_only_policy_wifi_networks_to_connect_if_available);
  }

  // Manually connecting to a non-policy network must be possible.
  EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
  EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
  EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig("wifi_policy_1"));

  ConnectToService(kServiceWifi1);
  EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateOnline);
  EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateIdle);
  EXPECT_EQ(GetWifiStateFromShillClient("wifi_policy_1"), shill::kStateIdle);

  // FakeShillManagerClient should not auto-reset the shill::kScanningProperty,
  // so the test can simulate a state where a scan has not finished yet.
  shill_manager_client_test_->SetAutoCompleteScan(false);

  // Sign-in a user and apply user ONC policy for another unavailable network.
  {
    LoginUser(test_account_id_);
    const std::string user_hash = GetTestUserHash();
    shill_profile_client_test_->AddProfile(kUserProfilePath, user_hash);

    const char kUserONC[] = R"(
        {
          "NetworkConfigurations": [
            {
              "GUID": "wifi_policy_2",
              "Name": "UserLevelWifi",
              "Type": "WiFi",
              "WiFi": {
                "HiddenSSID": false,
                "Passphrase": "UserLevelWifiPwd",
                "SSID": "PolicyUserLevelWifi",
                "Security": "WPA-PSK"
              }
            }
          ]
        })";

    SetUserOpenNetworkConfiguration(user_hash, kUserONC,
                                    /*wait_applied=*/true);
  }

  // WiFi1 should still be connected.
  EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
  EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
  EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig("wifi_policy_1"));
  EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig("wifi_policy_2"));

  EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateOnline);
  EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateIdle);
  EXPECT_EQ(GetWifiStateFromShillClient("wifi_policy_1"), shill::kStateIdle);
  EXPECT_EQ(GetWifiStateFromShillClient("wifi_policy_2"), shill::kStateIdle);

  // Wait for scanning to start (triggered by user login / user network policy
  // application).
  DeviceScanningWaiter(shill_device_client_test_, kDeviceWifiPath)
      .WaitForScanning();

  // A manual connection attempt should be blocked because a scan has not
  // finished yet, so it is not clear yet if
  // AllowOnlyPolicyNetworksToConnectIfAvailable should be enforced
  // (https://crbug.com/442695977).
  {
    const base::expected<void, std::string> manual_connect_result =
        ManualConnect(kServiceWifi2);
    EXPECT_FALSE(manual_connect_result.has_value())
        << "Expected manual connection attempt to be blocked until scan "
           "finished.";
    EXPECT_EQ(manual_connect_result.error(),
              ash::NetworkConnectionHandler::kErrorWaitingForScan);
  }

  {
    // Now the policy-provided network becomes visible in the wifi scan.
    // Expect that wifi_policy_2 connects.
    std::optional<std::string> user_policy_wifi_service_path =
        shill_service_client_test_->FindServiceMatchingGUID("wifi_policy_2");

    shill_manager_client_test_->SetBestServiceToConnect(
        user_policy_wifi_service_path.value());

    ASSERT_TRUE(user_policy_wifi_service_path);
    ServiceStateWaiter wifi_connected_waiter(
        shill_service_client_test_, user_policy_wifi_service_path.value());
    SetServiceVisibility("wifi_policy_2", true);
    SimulateWifiScanCompleted();

    wifi_connected_waiter.Wait(shill::kStateOnline);
  }

  // Expects that the non-policy WiFi services are now prohibited and that the
  // policy-provided network has connected.
  EXPECT_TRUE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
  EXPECT_TRUE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
  EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig("wifi_policy_1"));
  EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig("wifi_policy_2"));

  EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateIdle);
  EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateIdle);
  EXPECT_EQ(GetWifiStateFromShillClient("wifi_policy_1"), shill::kStateIdle);
  EXPECT_EQ(GetWifiStateFromShillClient("wifi_policy_2"), shill::kStateOnline);

  // A manual connection attempt to an unmanaged wifi should fail accordingly.
  {
    const base::expected<void, std::string> connect_after_scan_result =
        ManualConnect(kServiceWifi2);
    EXPECT_FALSE(connect_after_scan_result.has_value())
        << "Expected manual connection attempt to be blocked due to policy";
    EXPECT_EQ(connect_after_scan_result.error(),
              ash::NetworkConnectionHandler::kErrorBlockedByPolicy);
  }

  // Now the policy-provided network becomes invisible again, and no network is
  // prohibited anymore.
  SetServiceVisibility("wifi_policy_2", false);
  SimulateFullWifiScan();

  EXPECT_TRUE(base::test::RunUntil([=, this]() {
    return !IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1) &&
           !IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2) &&
           !IsProhibitedByPolicyInCrosNetworkConfig("wifi_policy_1") &&
           !IsProhibitedByPolicyInCrosNetworkConfig("wifi_policy_2");
  })) << "Timeout waiting for prohibited networks state."
      << " Checking conditions one by one:";
  EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
  EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
  EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig("wifi_policy_1"));
  EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig("wifi_policy_2"));

  // A manual connection attempt should succeed now because no policy-provided
  // network is visible and no initial scan is in progress.
  {
    const base::expected<void, std::string> connect_result =
        ManualConnect(kServiceWifi2);
    EXPECT_TRUE(connect_result.has_value())
        << "Expected manual connection attempt to succeed";
  }

  EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateIdle);
  EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateOnline);
  EXPECT_EQ(GetWifiStateFromShillClient("wifi_policy_1"), shill::kStateIdle);
  EXPECT_EQ(GetWifiStateFromShillClient("wifi_policy_2"), shill::kStateIdle);
}

// Behavior of AllowOnlyPolicyNetworksToConnectIfAvailable when a device policy
// network is visible on user login.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
                       OnlyPolicyIfAvailable_DeviceNetworkVisibleOnLogin) {
  constexpr char kGuidWifi1[] = "wifi_orig_guid_1";
  constexpr char kGuidWifi2[] = "wifi_orig_guid_2";

  CrosNetworkConfigGuidsAvailableWaiter available_waiter(
      cros_network_config_.get(), {kGuidWifi1, kGuidWifi2});
  AddPskWifiService(kServiceWifi1, kGuidWifi1, "WifiOne", shill::kStateIdle);
  AddPskWifiService(kServiceWifi2, kGuidWifi2, "WifiTwo", shill::kStateIdle);
  available_waiter.Wait();

  // Check that initially no policies applied and CrosNetworkStateProperties
  // has no prohibited networks.
  {
    EXPECT_FALSE(CrosNetworkConfigGetGlobalPolicy()
                     ->allow_only_policy_wifi_networks_to_connect);

    EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
    EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
  }

  // Apply device ONC policy
  {
    const char kDeviceONC[] = R"(
        {
          "GlobalNetworkConfiguration": {
            "AllowOnlyPolicyNetworksToConnectIfAvailable": true
          },
          "NetworkConfigurations": [
            {
              "GUID": "wifi_policy_1",
              "Name": "PolicyDeviceLevelWifi",
              "Type": "WiFi",
              "WiFi": {
                "HiddenSSID": false,
                "Passphrase": "DeviceLevelWifiPwd",
                "SSID": "PolicyDeviceLevelWifi",
                "Security": "WPA-PSK"
              }
            }
          ]
        })";
    SetDeviceOpenNetworkConfiguration(kDeviceONC, /*wait_applied=*/true);

    EXPECT_TRUE(CrosNetworkConfigGetGlobalPolicy()
                    ->allow_only_policy_wifi_networks_to_connect_if_available);
  }
  // Make the device policy network visible.
  SetServiceVisibility("wifi_policy_1", true);

  // Manually connecting to a non-policy network must be possible (this is still
  // on the sign-in screen).
  EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
  EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
  EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig("wifi_policy_1"));

  ConnectToService(kServiceWifi1);
  EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateOnline);
  EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateIdle);
  EXPECT_EQ(GetWifiStateFromShillClient("wifi_policy_1"), shill::kStateIdle);

  // `FakeShillManagerClient` should not auto-reset the
  // `shill::kScanningProperty`, so the test can simulate a state where a scan
  // has not finished yet.
  shill_manager_client_test_->SetAutoCompleteScan(false);

  // Sign-in a user. The device policy network should connect because the
  // AllowOnlyPolicyNetworksToConnectIfAvailable became effective on user login.
    std::optional<std::string> policy_wifi_service_path =
        shill_service_client_test_->FindServiceMatchingGUID("wifi_policy_1");
    ASSERT_TRUE(policy_wifi_service_path);
    ServiceStateWaiter wifi_connected_waiter(shill_service_client_test_,
                                             policy_wifi_service_path.value());

    shill_manager_client_test_->SetBestServiceToConnect(
        policy_wifi_service_path.value());

    {
      ScopedNetworkPolicyApplicationObserver
          network_policy_application_observer;

      LoginUser(test_account_id_);
      const std::string user_hash = GetTestUserHash();
      shill_profile_client_test_->AddProfile(kUserProfilePath, user_hash);

      // Even though the user doesn't have user ONC policy, an empty user policy
      // application will be signaled.
      network_policy_application_observer.WaitPoliciesApplied(user_hash);
    }

    // Wait for scanning to start (triggered by user login / user network policy
    // application).
    DeviceScanningWaiter(shill_device_client_test_, kDeviceWifiPath)
        .WaitForScanning();

    // A manual connection attempt should be blocked because a scan has not
    // finished yet, so it is not clear yet if
    // AllowOnlyPolicyNetworksToConnectIfAvailable should be enforced
    // (https://crbug.com/442695977).
    {
      const base::expected<void, std::string> manual_connect_result =
          ManualConnect(kServiceWifi2);
      EXPECT_FALSE(manual_connect_result.has_value())
          << "Expected manual connection attempt to be blocked until scan "
             "finished.";
      EXPECT_EQ(manual_connect_result.error(),
                ash::NetworkConnectionHandler::kErrorWaitingForScan);
    }

    // When scanning is done, the device policy provided network auto connects.
    SimulateWifiScanCompleted();

    wifi_connected_waiter.Wait(shill::kStateOnline);

    // Expects that the non-policy WiFi services are now prohibited.
    EXPECT_TRUE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
    EXPECT_TRUE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
    EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig("wifi_policy_1"));

    // Manually connecting to these fails accordingly.
    {
      const base::expected<void, std::string> connect_before_scan_result =
          ManualConnect(kServiceWifi2);
      EXPECT_FALSE(connect_before_scan_result.has_value())
          << "Expected manual connection attempt to be blocked by policy.";
      EXPECT_EQ(connect_before_scan_result.error(),
                ash::NetworkConnectionHandler::kErrorBlockedByPolicy);
    }
}

// Checks the edge case where a policy with GUID {same_guid} applies to network
// with SSID "WifiTwo", and subsequently the policy changes, the new
// NetworkConfiguration with GUID {same_guid} now applying to SSID "WifiOne".
// For this to work correctly, PolicyApplicator must first clear the "WifiTwo"
// settings so it is not matched by GUID, and then write the new policy to
// shill.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
                       PolicyWithSameGUIDAppliesToOtherSSID) {
  // Set up two services.
  AddPskWifiService(kServiceWifi1, "wifi_orig_guid_1", "WifiOne",
                    shill::kStateIdle);
  AddPskWifiService(kServiceWifi2, "wifi_orig_guid_2", "WifiTwo",
                    shill::kStateIdle);

  const char kDeviceONC1[] = R"(
    {
      "NetworkConfigurations": [
        {
          "GUID": "{same_guid}",
          "Name": "X",
          "Type": "WiFi",
          "WiFi": {
             "AutoConnect": false,
             "HiddenSSID": false,
             "Passphrase": "Passphrase",
             "SSID": "WifiTwo",
             "Security": "WPA-PSK"
          }
        }
      ]
    })";
  SetDeviceOpenNetworkConfiguration(kDeviceONC1, /*wait_applied=*/true);

  EXPECT_THAT(shill_service_client_test_->GetServiceProperties(kServiceWifi2),
              Pointee(DictionaryHasValue(shill::kGuidProperty,
                                         base::Value("{same_guid}"))));

  // Same GUID for a different SSID.
  const char kDeviceONC2[] = R"(
    {
      "NetworkConfigurations": [
        {
          "GUID": "{same_guid}",
          "Name": "X",
          "Type": "WiFi",
          "WiFi": {
             "AutoConnect": false,
             "HiddenSSID": false,
             "Passphrase": "SomePassphrase",
             "SSID": "WifiOne",
             "Security": "WPA-PSK"
          }
        }
      ]
    })";
  SetDeviceOpenNetworkConfiguration(kDeviceONC2, /*wait_applied=*/true);
  {
    const base::DictValue* wifi_service_properties =
        shill_service_client_test_->GetServiceProperties(kServiceWifi2);
    ASSERT_TRUE(wifi_service_properties);
    EXPECT_FALSE(wifi_service_properties->Find(shill::kGuidProperty));
  }
  EXPECT_THAT(shill_service_client_test_->GetServiceProperties(kServiceWifi1),
              Pointee(DictionaryHasValue(shill::kGuidProperty,
                                         base::Value("{same_guid}"))));
}

// Tests that application of policy settings does not wipe an already-configured
// client certificate. This is a regression test for b/203015922.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest, DoesNotWipeCertSettings) {
  const char* kCertKeyFilename = "client_3.pk8";
  const char* kCertFilename = "client_3.pem";
  const char* kCertIssuerCommonName = "E CA";
  ASSERT_NO_FATAL_FAILURE(ImportCert(net::GetTestCertsDirectory(),
                                     kCertFilename, kCertKeyFilename));

  // Set up a policy-managed EAP wifi with a certificate already selected.
  Add8021xWifiService(kServiceWifi1, "DeviceLevelWifiGuidOrig",
                      "DeviceLevelWifiSsid", shill::kStateOnline);

  ServicePropertyValueWatcher eap_cert_id_watcher(
      shill_service_client_test_, kServiceWifi1, shill::kEapCertIdProperty);
  ServicePropertyValueWatcher eap_key_id_watcher(
      shill_service_client_test_, kServiceWifi1, shill::kEapKeyIdProperty);
  ServicePropertyValueWatcher eap_identity_watcher(
      shill_service_client_test_, kServiceWifi1, shill::kEapIdentityProperty);

  SetDeviceOpenNetworkConfiguration(
      OncPolicyToSelectClientCert("DeviceLevelWifiGuid", "DeviceLevelWifiSsid",
                                  /*issuer_common_name=*/kCertIssuerCommonName,
                                  /*identity=*/"identity_1"),
      /*wait_applied=*/true);

  // Verify that the EAP.CertId and EAP.KeyId properties are present and not
  // empty, i.e. that a client certificate has been selected.
  ASSERT_THAT(eap_cert_id_watcher.GetValues(), ElementsAre(Not(IsEmpty())));
  ASSERT_THAT(eap_key_id_watcher.GetValues(), ElementsAre(Not(IsEmpty())));
  std::string orig_eap_cert_id = eap_cert_id_watcher.GetValues().back();
  std::string orig_eap_key_id = eap_key_id_watcher.GetValues().back();

  EXPECT_THAT(eap_identity_watcher.GetValues(), ElementsAre("identity_1"));

  SetDeviceOpenNetworkConfiguration(
      OncPolicyToSelectClientCert("DeviceLevelWifiGuid", "DeviceLevelWifiSsid",
                                  /*issuer_common_name=*/kCertIssuerCommonName,
                                  /*identity=*/"identity_2"),
      /*wait_applied=*/true);

  // Verify that the EAP.CertId and EAP.KeyId properties have not been changed
  // to anything else (also not an empty string).
  ASSERT_THAT(eap_cert_id_watcher.GetValues(), ElementsAre(orig_eap_cert_id));
  ASSERT_THAT(eap_key_id_watcher.GetValues(), ElementsAre(orig_eap_key_id));
  EXPECT_THAT(eap_identity_watcher.GetValues(),
              ElementsAre("identity_1", "identity_2"));
}

// Configures a device-wide network that uses variable expansions
// (https://chromium.googlesource.com/chromium/src/+/main/components/onc/docs/onc_spec.md#string-expansions)
// and then tests that these variables are replaced with their values in the
// config pushed to shill.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
                       DevicePolicyProfileWideVariableExpansions) {
  const std::string kSerialNumber = "test_serial";
  ash::system::ScopedFakeStatisticsProvider fake_statistics_provider_;
  fake_statistics_provider_.SetMachineStatistic(ash::system::kSerialNumberKey,
                                                kSerialNumber);

  Add8021xWifiService(kServiceWifi1, "DeviceLevelWifiGuidOrig",
                      "DeviceLevelWifiSsid", shill::kStateIdle);

  SetDeviceOpenNetworkConfiguration(
      OncPolicyToSelectClientCert("{DeviceLevelWifiGuid}",
                                  "DeviceLevelWifiSsid",
                                  /*issuer_common_name=*/"Example Inc.",
                                  /*identity=*/"${DEVICE_SERIAL_NUMBER}"),
      /*wait_applied=*/true);

  EXPECT_THAT(shill_service_client_test_->GetServiceProperties(kServiceWifi1),
              Pointee(DictionaryHasValues(
                  base::DictValue()
                      .Set(shill::kGuidProperty, "{DeviceLevelWifiGuid}")
                      // Expect that the EAP.Identity has been replaced
                      .Set(shill::kEapIdentityProperty, kSerialNumber))));

  // TODO(b/209084821): Also test DEVICE_ASSET_ID when it's easily
  // configurable in a browsertest.
}

// Configures a network that uses variable expansions with variables based on a
// client certificate selected using a CertificatePattern.
// The network is device-wide because that is easier to set up in the test.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
                       DevicePolicyCertBasedVariableExpansions) {
  const char* kCertKeyFilename = "client_3.pk8";
  const char* kCertFilename = "client_3.pem";
  const char* kCertIssuerCommonName = "E CA";
  const char* kIdentityPolicyValue =
      "${CERT_SUBJECT_COMMON_NAME}/${CERT_SAN_UPN}/${CERT_SAN_EMAIL}";
  const char* kExpectedIdentity =
      "Client Cert F/santest@ad.corp.example.com/santest@example.com";
  ASSERT_NO_FATAL_FAILURE(ImportCert(net::GetTestCertsDirectory(),
                                     kCertFilename, kCertKeyFilename));

  Add8021xWifiService(kServiceWifi1, "DeviceLevelWifiGuidOrig",
                      "DeviceLevelWifiSsid", shill::kStateIdle);

  SetDeviceOpenNetworkConfiguration(
      OncPolicyToSelectClientCert("{DeviceLevelWifiGuid}",
                                  "DeviceLevelWifiSsid",
                                  /*issuer_common_name=*/kCertIssuerCommonName,
                                  /*identity=*/kIdentityPolicyValue),
      /*wait_applied=*/true);
  ServicePropertyValueWatcher(shill_service_client_test_, kServiceWifi1,
                              shill::kEapCertIdProperty)
      .WaitForNonEmptyValue();

  EXPECT_THAT(shill_service_client_test_->GetServiceProperties(kServiceWifi1),
              Pointee(DictionaryHasValues(
                  base::DictValue()
                      .Set(shill::kGuidProperty, "{DeviceLevelWifiGuid}")
                      // Expect that the EAP.Identity has been replaced
                      .Set(shill::kEapIdentityProperty, kExpectedIdentity))));
}

// Configures a user-specific network that uses variable expansions
// (https://chromium.googlesource.com/chromium/src/+/main/components/onc/docs/onc_spec.md#string-expansions)
// and then tests that these variables are replaced with their values in the
// config pushed to shill.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
                       UserPolicyProfileWideVariableExpansions) {
  Add8021xWifiService(kServiceWifi1, "UserLevelWifiGuidOrig",
                      "UserLevelWifiSsid", shill::kStateIdle);

  LoginUser(test_account_id_);
  const std::string user_hash = user_manager::UserManager::Get()
                                    ->FindUser(test_account_id_)
                                    ->username_hash();
  shill_profile_client_test_->AddProfile(kUserProfilePath, user_hash);

  // Note that while a policy is used here that uses a ClientCertPattern, no
  // client certificate is resolved (because no such certificate was imported).
  // Still, the "user-specific variable" should be expanded.
  SetUserOpenNetworkConfiguration(
      user_hash,
      OncPolicyToSelectClientCert("{UserLevelWifiGuid}", "UserLevelWifiSsid",
                                  /*issuer_common_name=*/"Example Inc.",
                                  /*identity=*/"${LOGIN_EMAIL}"),
      /*wait_applied=*/true);

  EXPECT_THAT(shill_service_client_test_->GetServiceProperties(kServiceWifi1),
              Pointee(DictionaryHasValues(
                  base::DictValue()
                      .Set(shill::kGuidProperty, "{UserLevelWifiGuid}")
                      // Expect that the EAP.Identity has been replaced
                      .Set(shill::kEapIdentityProperty,
                           test_account_id_.GetUserEmail()))));
}

// Tests that re-applying Ethernet policy retains a manually-set IP address.
// This is a regression test for b/183676832 and b/180365271.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest, RetainEthernetIPAddr) {
  constexpr char kEthernetGuid[] = "{EthernetGuid}";

  shill_service_client_test_->AddService(kServiceEth, "orig_guid_ethernet_any",
                                         "ethernet_any", shill::kTypeEthernet,
                                         shill::kStateOnline, /*visible=*/true);

  {
    std::string kDeviceONCEverythingRecommended =
        base::StringPrintf(R"(
    {
      "NetworkConfigurations": [
        {
          "GUID": "%s",
          "Name": "EthernetName",
          "Type": "Ethernet",
          "Ethernet": {
             "Authentication": "None"
          },
          "StaticIPConfig": {
             "Recommended": ["Gateway", "IPAddress", "RoutingPrefix",
                             "NameServers"]
          },
          "Recommended": ["IPAddressConfigType", "NameServersConfigType"]
        }
      ]
    })",

                           kEthernetGuid);
    SetDeviceOpenNetworkConfiguration(kDeviceONCEverythingRecommended,
                                      /*wait_applied=*/true);
  }

  EXPECT_THAT(shill_service_client_test_->GetServiceProperties(kServiceEth),
              Pointee(DictionaryHasValue(shill::kGuidProperty,
                                         base::Value(kEthernetGuid))));

  // Check that IP address is modifiable and policy-recommended.
  {
    auto properties = CrosNetworkConfigGetManagedProperties("{EthernetGuid}");
    ASSERT_TRUE(properties);
    EXPECT_EQ(properties->ip_address_config_type->policy_source,
              network_mojom::PolicySource::kDevicePolicyRecommended);
  }

  // Simulate setting an IP addr through the UI.
  {
    auto properties = network_mojom::ConfigProperties::New();
    properties->type_config =
        network_mojom::NetworkTypeConfigProperties::NewEthernet(
            network_mojom::EthernetConfigProperties::New());
    properties->ip_address_config_type =
        ::onc::network_config::kIPConfigTypeStatic;
    properties->static_ip_config = network_mojom::IPConfigProperties::New();
    properties->static_ip_config->ip_address = "192.168.1.44";
    properties->static_ip_config->gateway = "192.168.1.1";
    properties->static_ip_config->routing_prefix = 4;
    ASSERT_NO_FATAL_FAILURE(
        CrosNetworkConfigSetProperties(kEthernetGuid, std::move(properties)));
  }

  // Verify that the Static IP config has been applied.
  {
    const base::DictValue* shill_properties =
        shill_service_client_test_->GetServiceProperties(kServiceEth);
    ASSERT_TRUE(shill_properties);
    EXPECT_EQ(GetStaticIPAddressFromShillProperties(*shill_properties),
              "192.168.1.44");
  }

  {
    // Modify the policy: Force custom nameserver, but allow IP address to be
    // modifiable.
    std::string kDeviceONC2 = base::StringPrintf(R"(
    {
      "NetworkConfigurations": [
        {
          "GUID": "%s",
          "Name": "EthernetName",
          "Type": "Ethernet",
          "Ethernet": {
             "Authentication": "None"
          },
          "StaticIPConfig": {
             "NameServers": ["8.8.3.1", "8.8.2.1"],
             "Recommended": ["Gateway", "IPAddress", "RoutingPrefix"]
          },
          "NameServersConfigType": "Static",
          "Recommended": ["IPAddressConfigType"]
        }
      ]
    })",
                                                 kEthernetGuid);
    SetDeviceOpenNetworkConfiguration(kDeviceONC2, /*wait_applied=*/true);
  }

  // Verify that the Static IP is still active, and the custom name server has
  // been applied.
  {
    const base::DictValue* shill_properties =
        shill_service_client_test_->GetServiceProperties(kServiceEth);
    ASSERT_TRUE(shill_properties);
    EXPECT_EQ(GetStaticIPAddressFromShillProperties(*shill_properties),
              "192.168.1.44");
    EXPECT_THAT(GetStaticNameServersFromShillProperties(*shill_properties),
                ElementsAre("8.8.3.1", "8.8.2.1", "0.0.0.0", "0.0.0.0"));
  }

  // Modify the policy: Force DHCP ip address
  const char kDeviceONC3[] = R"(
    {
      "NetworkConfigurations": [
        {
          "GUID": "{EthernetGuid}",
          "Name": "EthernetName",
          "Type": "Ethernet",
          "Ethernet": {
             "Authentication": "None"
          },
          "StaticIPConfig": {
             "Recommended": []
          }
        }
      ]
    })";
  SetDeviceOpenNetworkConfiguration(kDeviceONC3, /*wait_applied=*/true);

  // Check that IP address is not modifiable.
  {
    auto properties = CrosNetworkConfigGetManagedProperties("{EthernetGuid}");
    ASSERT_TRUE(properties);
    EXPECT_EQ(properties->ip_address_config_type->policy_source,
              network_mojom::PolicySource::kDevicePolicyEnforced);
  }

  // Verify that the Static IP is gone.
  {
    const base::DictValue* shill_properties =
        shill_service_client_test_->GetServiceProperties(kServiceEth);
    ASSERT_TRUE(shill_properties);
    EXPECT_THAT(GetStaticIPAddressFromShillProperties(*shill_properties),
                IsEmpty());
  }
}

// Tests that re-applying Ethernet policy retains a manually-set IP address,
// even if multiple eth services exist. This is a regression test for
// b/467741000.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
                       RetainEthernetIPAddrWithMultipleEthServices) {
  // Set the properties for an IP Config object.
  const std::string kTestIPConfigPath("test_ip_config_path");
  const std::string kEtheretAnyService("/service/100");
  const std::string kInitialIpAddr("192.168.1.10");

  base::DictValue ipv4;
  ipv4.Set(shill::kAddressProperty, kInitialIpAddr);
  ipv4.Set(shill::kMethodProperty, shill::kTypeIPv4);

  shill_ip_config_client_test_->AddIPConfig(kTestIPConfigPath, std::move(ipv4));
  // chrome  / policy_applicator.cc needs the static IP config to be in "UIData"
  // too..
  base::DictValue ui_data = CreateUIDataWithIPAddr(kInitialIpAddr);

  constexpr char kEthernetGuid[] = "{EthernetGuid}";

  shill_service_client_test_->AddServiceWithIPConfig(
      kEtheretAnyService, "orig_guid_ethernet_any", "ethernet_any",
      shill::kTypeEthernet, shill::kStateOnline, kTestIPConfigPath,
      /*visible=*/true);
  ASSERT_NO_FATAL_FAILURE(SetUIDataDict(kEtheretAnyService, ui_data));
  shill_profile_client_test_->AddService(kSharedProfilePath,
                                         kEtheretAnyService);
  shill_service_client_test_->AddService(kServiceEth1, "orig_guid_ethernet_1",
                                         "ethernet_1", shill::kTypeEthernet,
                                         shill::kStateIdle, /*visible=*/true);
  shill_profile_client_test_->AddService(kSharedProfilePath, kServiceEth1);
  shill_service_client_test_->AddService(kServiceEth2, "orig_guid_ethernet_2",
                                         "ethernet_2", shill::kTypeEthernet,
                                         shill::kStateIdle, /*visible=*/true);
  shill_profile_client_test_->AddService(kSharedProfilePath, kServiceEth2);

  {
    std::string kDeviceONCEverythingRecommended =
        base::StringPrintf(R"(
    {
      "NetworkConfigurations": [
        {
          "GUID": "%s",
          "Name": "EthernetName",
          "Type": "Ethernet",
          "Ethernet": {
             "Authentication": "None"
          },
          "StaticIPConfig": {
             "Recommended": ["Gateway", "IPAddress", "RoutingPrefix",
                             "NameServers"]
          },
          "Recommended": ["IPAddressConfigType", "NameServersConfigType"]
        }
      ]
    })",

                           kEthernetGuid);

    ConfigureServiceObserver observer(shill_manager_client_test_,
                                      shill::kTypeEthernet);
    SetDeviceOpenNetworkConfiguration(kDeviceONCEverythingRecommended,
                                      /*wait_applied=*/true);
    // Verify that a ConfigureService call happened only for one Eth service
    // (shill can only persist one eth configuration at the moment).
    EXPECT_EQ(observer.GetConfigureCount(), 1);
  }

  EXPECT_THAT(
      shill_service_client_test_->GetServiceProperties(kEtheretAnyService),
      Pointee(DictionaryHasValue(shill::kGuidProperty,
                                 base::Value(kEthernetGuid))));

  // Check that IP address is modifiable and policy-recommended.
  {
    auto properties = CrosNetworkConfigGetManagedProperties("{EthernetGuid}");
    ASSERT_TRUE(properties);
    EXPECT_EQ(properties->ip_address_config_type->policy_source,
              network_mojom::PolicySource::kDevicePolicyRecommended);
    EXPECT_EQ(properties->static_ip_config->ip_address->active_value,
              kInitialIpAddr);
  }

  // Simulate setting another IP addr through the UI.
  {
    auto properties = network_mojom::ConfigProperties::New();
    properties->type_config =
        network_mojom::NetworkTypeConfigProperties::NewEthernet(
            network_mojom::EthernetConfigProperties::New());
    properties->ip_address_config_type =
        ::onc::network_config::kIPConfigTypeStatic;
    properties->static_ip_config = network_mojom::IPConfigProperties::New();
    properties->static_ip_config->ip_address = "192.168.1.44";
    properties->static_ip_config->gateway = "192.168.1.1";
    properties->static_ip_config->routing_prefix = 4;
    ASSERT_NO_FATAL_FAILURE(
        CrosNetworkConfigSetProperties(kEthernetGuid, std::move(properties)));
  }

  // Verify that the Static IP config has been applied.
  {
    const base::DictValue* shill_properties =
        shill_service_client_test_->GetServiceProperties(kEtheretAnyService);
    ASSERT_TRUE(shill_properties);
    EXPECT_EQ(GetStaticIPAddressFromShillProperties(*shill_properties),
              "192.168.1.44");
  }

  {
    // Modify the policy: Force custom nameserver, but allow IP address to be
    // modifiable.
    std::string kDeviceONC2 = base::StringPrintf(R"(
    {
      "NetworkConfigurations": [
        {
          "GUID": "%s",
          "Name": "EthernetName",
          "Type": "Ethernet",
          "Ethernet": {
             "Authentication": "None"
          },
          "StaticIPConfig": {
             "NameServers": ["8.8.3.1", "8.8.2.1"],
             "Recommended": ["Gateway", "IPAddress", "RoutingPrefix"]
          },
          "NameServersConfigType": "Static",
          "Recommended": ["IPAddressConfigType"]
        }
      ]
    })",
                                                 kEthernetGuid);
    SetDeviceOpenNetworkConfiguration(kDeviceONC2, /*wait_applied=*/true);
  }

  // Verify that the Static IP is still active, and the custom name server has
  // been applied.
  {
    const base::DictValue* shill_properties =
        shill_service_client_test_->GetServiceProperties(kEtheretAnyService);
    ASSERT_TRUE(shill_properties);
    EXPECT_EQ(GetStaticIPAddressFromShillProperties(*shill_properties),
              "192.168.1.44");
    EXPECT_THAT(GetStaticNameServersFromShillProperties(*shill_properties),
                ElementsAre("8.8.3.1", "8.8.2.1", "0.0.0.0", "0.0.0.0"));
  }

  // Modify the policy: Force DHCP ip address
  const char kDeviceONC3[] = R"(
    {
      "NetworkConfigurations": [
        {
          "GUID": "{EthernetGuid}",
          "Name": "EthernetName",
          "Type": "Ethernet",
          "Ethernet": {
             "Authentication": "None"
          },
          "StaticIPConfig": {
             "Recommended": []
          }
        }
      ]
    })";
  SetDeviceOpenNetworkConfiguration(kDeviceONC3, /*wait_applied=*/true);

  // Check that IP address is not modifiable.
  {
    auto properties = CrosNetworkConfigGetManagedProperties("{EthernetGuid}");
    ASSERT_TRUE(properties);
    EXPECT_EQ(properties->ip_address_config_type->policy_source,
              network_mojom::PolicySource::kDevicePolicyEnforced);
  }

  // Verify that the Static IP is gone.
  {
    const base::DictValue* shill_properties =
        shill_service_client_test_->GetServiceProperties(kEtheretAnyService);
    ASSERT_TRUE(shill_properties);
    EXPECT_THAT(GetStaticIPAddressFromShillProperties(*shill_properties),
                IsEmpty());
  }
}

// Tests that re-applying network policy sends back all eth entries to shill if
// the feature kFixStaticIpForTwoManagedEthPorts is disabled.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationNoEthFixTest,
                       AllEthEntriesReapplied) {
  // Set the properties for an IP Config object.
  const std::string kTestIPConfigPath("test_ip_config_path");
  const std::string kEtheretAnyService("/service/100");
  const std::string kInitialIpAddr("192.168.1.10");

  base::DictValue ipv4;
  ipv4.Set(shill::kAddressProperty, kInitialIpAddr);
  ipv4.Set(shill::kMethodProperty, shill::kTypeIPv4);

  shill_ip_config_client_test_->AddIPConfig(kTestIPConfigPath, std::move(ipv4));
  // chrome  / policy_applicator.cc needs the static IP config to be in "UIData"
  // too..
  base::DictValue ui_data = CreateUIDataWithIPAddr(kInitialIpAddr);

  constexpr char kEthernetGuid[] = "{EthernetGuid}";

  shill_service_client_test_->AddServiceWithIPConfig(
      kEtheretAnyService, "orig_guid_ethernet_any", "ethernet_any",
      shill::kTypeEthernet, shill::kStateOnline, kTestIPConfigPath,
      /*visible=*/true);
  ASSERT_NO_FATAL_FAILURE(SetUIDataDict(kEtheretAnyService, ui_data));
  shill_profile_client_test_->AddService(kSharedProfilePath,
                                         kEtheretAnyService);
  shill_service_client_test_->AddService(kServiceEth1, "orig_guid_ethernet_1",
                                         "ethernet_1", shill::kTypeEthernet,
                                         shill::kStateIdle, /*visible=*/true);
  shill_profile_client_test_->AddService(kSharedProfilePath, kServiceEth1);
  shill_service_client_test_->AddService(kServiceEth2, "orig_guid_ethernet_2",
                                         "ethernet_2", shill::kTypeEthernet,
                                         shill::kStateIdle, /*visible=*/true);
  shill_profile_client_test_->AddService(kSharedProfilePath, kServiceEth2);

  {
    std::string kDeviceONCEverythingRecommended =
        base::StringPrintf(R"(
    {
      "NetworkConfigurations": [
        {
          "GUID": "%s",
          "Name": "EthernetName",
          "Type": "Ethernet",
          "Ethernet": {
             "Authentication": "None"
          },
          "StaticIPConfig": {
             "Recommended": ["Gateway", "IPAddress", "RoutingPrefix",
                             "NameServers"]
          },
          "Recommended": ["IPAddressConfigType", "NameServersConfigType"]
        }
      ]
    })",

                           kEthernetGuid);

    ConfigureServiceObserver observer(shill_manager_client_test_,
                                      shill::kTypeEthernet);
    SetDeviceOpenNetworkConfiguration(kDeviceONCEverythingRecommended,
                                      /*wait_applied=*/true);

    // Verify that a ConfigureService call happened once per existing Eth
    // service.
    EXPECT_EQ(observer.GetConfigureCount(), 3);
  }
}

// Tests that Ethernet fixes the 'GUID' in 'UIData', if another GUID was
// persisted due to a bug.
// Note: UIData is a String property that chrome fills with a serialized
// dictionary.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest, FixEthernetUIDataGUID) {
  constexpr char kEthernetGuid[] = "{EthernetGuid}";

  shill_service_client_test_->AddService(kServiceEth, "orig_guid_ethernet_any",
                                         "ethernet_any", shill::kTypeEthernet,
                                         shill::kStateOnline, /*visible=*/true);

  // Apply Ethernet policy with a GUID.
  std::string kDeviceONC1 = base::StringPrintf(R"(
    {
      "NetworkConfigurations": [
        {
          "GUID": "%s",
          "Name": "EthernetName",
          "Type": "Ethernet",
          "Ethernet": {
             "Authentication": "None"
          }
        }
      ]
    })",
                                               kEthernetGuid);
  SetDeviceOpenNetworkConfiguration(kDeviceONC1, /*wait_applied=*/true);

  // Set GUID in the "user_settings" part of the UIData dictionary to a
  // inconsistent value.
  {
    std::optional<base::DictValue> ui_data = GetUIDataDict(kServiceEth);
    ASSERT_TRUE(ui_data);
    base::DictValue* user_settings =
        ui_data->EnsureDict(kUIDataKeyUserSettings);
    user_settings->Set(::onc::network_config::kGUID, "wrong-guid");
    ASSERT_NO_FATAL_FAILURE(SetUIDataDict(kServiceEth, *ui_data));
  }

  // Verify that UIData now has the incorrect GUID.
  {
    std::optional<base::DictValue> ui_data = GetUIDataDict(kServiceEth);
    ASSERT_TRUE(ui_data);
    EXPECT_NE(GetGUIDFromUIData(*ui_data), kEthernetGuid);
  }

  // Re-apply Ethernet policy.
  std::string kDeviceONC2 = base::StringPrintf(R"(
    {
      "NetworkConfigurations": [
        {
          "GUID": "%s",
          "Name": "EthernetName",
          "Type": "Ethernet",
          "Ethernet": {
            "Authentication": "None",
            "StaticIPConfig": {
               "NameServers": ["8.8.3.1", "8.8.2.1"],
               "Recommended": ["Gateway", "IPAddress", "RoutingPrefix"]
            },
            "NameServersConfigType": "Static",
            "Recommended": ["IPAddressConfigType"]
          }
        }
      ]
    })",
                                               kEthernetGuid);
  SetDeviceOpenNetworkConfiguration(kDeviceONC2, /*wait_applied=*/true);

  // Check that GUID in the UIData dictionary has been fixed.
  {
    std::optional<base::DictValue> ui_data = GetUIDataDict(kServiceEth);
    ASSERT_TRUE(ui_data);
    EXPECT_EQ(GetGUIDFromUIData(*ui_data), kEthernetGuid);
  }
}

// Tests that a client certificate that has been resolved from a
// ClientCertPattern gets propated to the UI, specifically to the
// CrosNetworkConfig layer.
IN_PROC_BROWSER_TEST_F(
    NetworkPolicyApplicationTest,
    DevicePolicyClientCertPatternSuccessResultPropagatedToUi) {
  const char* kCertKeyFilename = "client_3.pk8";
  const char* kCertFilename = "client_3.pem";
  const char* kCertIssuerCommonName = "E CA";
  const char* kCertSubjectCommonName = "Client Cert F";
  ASSERT_NO_FATAL_FAILURE(ImportCert(net::GetTestCertsDirectory(),
                                     kCertFilename, kCertKeyFilename));

  Add8021xWifiService(kServiceWifi1, "DeviceLevelWifiGuidOrig",
                      "DeviceLevelWifiSsid", shill::kStateOnline);

  SetDeviceOpenNetworkConfiguration(
      OncPolicyToSelectClientCert("{DeviceLevelWifiGuid}",
                                  "DeviceLevelWifiSsid",
                                  /*issuer_common_name=*/kCertIssuerCommonName,
                                  /*identity=*/"TestIdentity"),
      /*wait_applied=*/true);

  {
    auto expected_client_cert =
        CrosNetworkConfigFindClientCert(kCertSubjectCommonName);
    ASSERT_TRUE(expected_client_cert)
        << "Couldn't find cert " << kCertSubjectCommonName;

    auto properties =
        CrosNetworkConfigGetManagedProperties("{DeviceLevelWifiGuid}");
    ASSERT_TRUE(properties);
    ASSERT_EQ(properties->type_properties->which(),
              network_mojom::NetworkTypeManagedProperties::Tag::kWifi);
    const auto& eap = properties->type_properties->get_wifi()->eap;

    // Check the selected certificate
    ASSERT_TRUE(eap->client_cert_pkcs11_id);
    EXPECT_EQ(eap->client_cert_pkcs11_id->active_value,
              expected_client_cert->pem_or_id);
    EXPECT_EQ(eap->client_cert_pkcs11_id->policy_source,
              network_mojom::PolicySource::kDevicePolicyEnforced);
    ASSERT_EQ(eap->client_cert_pkcs11_id->policy_value,
              std::make_optional(expected_client_cert->pem_or_id));

    // The type should be "PKCS11Id" in the UI.
    ASSERT_TRUE(eap->client_cert_type);
    EXPECT_EQ(eap->client_cert_type->active_value, onc::client_cert::kPKCS11Id);
    EXPECT_EQ(eap->client_cert_type->policy_source,
              network_mojom::PolicySource::kDevicePolicyEnforced);
    EXPECT_EQ(eap->client_cert_type->policy_value, onc::client_cert::kPKCS11Id);
  }
}

// Tests that resolution of a ClientCertPattern is tirggered when a client
// certificate is imported.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
                       DevicePolicyClientCertResolutionTriggeredOnCertImport) {
  const char* kCertKeyFilename = "client_3.pk8";
  const char* kCertFilename = "client_3.pem";
  const char* kCertIssuerCommonName = "E CA";
  const char* kCertSubjectCommonName = "Client Cert F";

  Add8021xWifiService(kServiceWifi1, "DeviceLevelWifiGuidOrig",
                      "DeviceLevelWifiSsid", shill::kStateOnline);

  SetDeviceOpenNetworkConfiguration(
      OncPolicyToSelectClientCert("{DeviceLevelWifiGuid}",
                                  "DeviceLevelWifiSsid",
                                  /*issuer_common_name=*/kCertIssuerCommonName,
                                  /*identity=*/"TestIdentity"),
      /*wait_applied=*/true);

  {
    // The  ClientCertPattern could not be resolved yet, the UI (the
    // CrosNetworkConfig layer) gets an empty PKCS11Id.
    auto properties =
        CrosNetworkConfigGetManagedProperties("{DeviceLevelWifiGuid}");
    ASSERT_TRUE(properties);
    ASSERT_EQ(properties->type_properties->which(),
              network_mojom::NetworkTypeManagedProperties::Tag::kWifi);
    const auto& eap = properties->type_properties->get_wifi()->eap;

    // Check that the selected certificate field is empty.
    ASSERT_TRUE(eap->client_cert_pkcs11_id);
    EXPECT_EQ(eap->client_cert_pkcs11_id->active_value, std::string());
    EXPECT_EQ(eap->client_cert_pkcs11_id->policy_source,
              network_mojom::PolicySource::kDevicePolicyEnforced);
    EXPECT_EQ(eap->client_cert_pkcs11_id->policy_value, std::string());

    // The type should still be "PKCS11Id" for the UI layer to indicate an empty
    // selection.
    ASSERT_TRUE(eap->client_cert_type);
    EXPECT_EQ(eap->client_cert_type->active_value, onc::client_cert::kPKCS11Id);
    EXPECT_EQ(eap->client_cert_type->policy_source,
              network_mojom::PolicySource::kDevicePolicyEnforced);
    EXPECT_EQ(eap->client_cert_type->policy_value, onc::client_cert::kPKCS11Id);
  }

  ServicePropertyValueWatcher eap_cert_id_watcher(
      shill_service_client_test_, kServiceWifi1, shill::kEapCertIdProperty);
  ASSERT_NO_FATAL_FAILURE(ImportCert(net::GetTestCertsDirectory(),
                                     kCertFilename, kCertKeyFilename));
  eap_cert_id_watcher.WaitForNonEmptyValue();

  {
    auto expected_client_cert =
        CrosNetworkConfigFindClientCert(kCertSubjectCommonName);
    ASSERT_TRUE(expected_client_cert)
        << "Couldn't find cert " << kCertSubjectCommonName;

    auto properties =
        CrosNetworkConfigGetManagedProperties("{DeviceLevelWifiGuid}");
    ASSERT_TRUE(properties);
    ASSERT_EQ(properties->type_properties->which(),
              network_mojom::NetworkTypeManagedProperties::Tag::kWifi);
    const auto& eap = properties->type_properties->get_wifi()->eap;

    // Check the selected certificate
    ASSERT_TRUE(eap->client_cert_pkcs11_id);
    EXPECT_EQ(eap->client_cert_pkcs11_id->active_value,
              expected_client_cert->pem_or_id);
    EXPECT_EQ(eap->client_cert_pkcs11_id->policy_source,
              network_mojom::PolicySource::kDevicePolicyEnforced);
    ASSERT_EQ(eap->client_cert_pkcs11_id->policy_value,
              std::make_optional(expected_client_cert->pem_or_id));

    // The type should be "PKCS11Id" in the UI.
    ASSERT_TRUE(eap->client_cert_type);
    EXPECT_EQ(eap->client_cert_type->active_value, onc::client_cert::kPKCS11Id);
    EXPECT_EQ(eap->client_cert_type->policy_source,
              network_mojom::PolicySource::kDevicePolicyEnforced);
    EXPECT_EQ(eap->client_cert_type->policy_value, onc::client_cert::kPKCS11Id);
  }
}

IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
                       RetainEthernetIPAddrUnmanagedToManaged) {
  constexpr char kEthernetGuidUnmanaged[] = "{orig_guid_ethernet_any}";
  constexpr char kEthernetGuidManaged[] = "{EthernetGuid}";
  constexpr char kStaticIpAddr[] = "192.168.1.44";

  CrosNetworkConfigGuidsAvailableWaiter available_waiter(
      cros_network_config_.get(), {kEthernetGuidUnmanaged});
  shill_service_client_test_->AddService(kServiceEth, kEthernetGuidUnmanaged,
                                         "ethernet_any", shill::kTypeEthernet,
                                         shill::kStateOnline, /*visible=*/true);
  shill_profile_client_test_->AddService(kSharedProfilePath, kServiceEth);
  available_waiter.Wait();

  // Check that IP address is modifiable and does not come from policy
  {
    auto properties =
        CrosNetworkConfigGetManagedProperties(kEthernetGuidUnmanaged);
    ASSERT_TRUE(properties);
    EXPECT_EQ(properties->ip_address_config_type->policy_source,
              network_mojom::PolicySource::kNone);
  }

  // Simulate setting an IP address through the UI.
  {
    auto properties = network_mojom::ConfigProperties::New();
    properties->type_config =
        network_mojom::NetworkTypeConfigProperties::NewEthernet(
            network_mojom::EthernetConfigProperties::New());
    properties->ip_address_config_type =
        ::onc::network_config::kIPConfigTypeStatic;
    properties->static_ip_config = network_mojom::IPConfigProperties::New();
    properties->static_ip_config->ip_address = kStaticIpAddr;
    properties->static_ip_config->gateway = "192.168.1.1";
    properties->static_ip_config->routing_prefix = 4;
    ASSERT_NO_FATAL_FAILURE(CrosNetworkConfigSetProperties(
        kEthernetGuidUnmanaged, std::move(properties)));
  }

  // Verify that the Static IP config has been applied.
  {
    const base::DictValue* shill_properties =
        shill_service_client_test_->GetServiceProperties(kServiceEth);
    ASSERT_TRUE(shill_properties);
    EXPECT_EQ(GetStaticIPAddressFromShillProperties(*shill_properties),
              kStaticIpAddr);
  }

  // Apply policy: Explicitly recommend both IP address and Nameservers,
  // allowing the user to modify them.
  const std::string kDeviceONCEverythingRecommended =
      base::StringPrintf(R"(
    {
      "NetworkConfigurations": [
        {
          "GUID": "%s",
          "Name": "EthernetName",
          "Type": "Ethernet",
          "Ethernet": {
             "Authentication": "None"
          },
          "StaticIPConfig": {
             "Recommended": ["Gateway", "IPAddress", "RoutingPrefix",
                             "NameServers"]
          },
          "Recommended": ["IPAddressConfigType", "NameServersConfigType"]
        }
      ]
    })",
                         kEthernetGuidManaged);
  SetDeviceOpenNetworkConfiguration(kDeviceONCEverythingRecommended,
                                    /*wait_applied=*/true);

  // Verify that the Static IP is still active.
  {
    const base::DictValue* shill_properties =
        shill_service_client_test_->GetServiceProperties(kServiceEth);
    ASSERT_TRUE(shill_properties);
    EXPECT_EQ(GetStaticIPAddressFromShillProperties(*shill_properties),
              kStaticIpAddr);
  }

  // Check that IP address is modifiable and "Recommended" by policy
  {
    auto properties =
        CrosNetworkConfigGetManagedProperties(kEthernetGuidManaged);
    ASSERT_TRUE(properties);
    EXPECT_EQ(properties->ip_address_config_type->policy_source,
              network_mojom::PolicySource::kDevicePolicyRecommended);
  }
}

IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
                       EphemeralActions_NotActive) {
  constexpr char kGuidWifi1[] = "guid_wifi_1";
  AddSharedDevicePolicyMschapv2Service(kServiceWifi1, kGuidWifi1, "wifi1",
                                       kUserIdentity);
  AddPskWifiService(kServiceWifi2, "unmanaged_wifi2_guid", "UnmanagedWifi2",
                    shill::kStateIdle);
  shill_profile_client_test_->AddService(kSharedProfilePath, kServiceWifi2);

  const std::string kDeviceONC = base::StringPrintf(
      R"(
      {
        "GlobalNetworkConfiguration": {
        },
        "NetworkConfigurations": [
          {
            "GUID": "%s",
            "Type": "WiFi",
            "Name": "Managed wifi1",
            "WiFi": {
              "HexSSID": "7769666931", // "wifi1"
              "SSID": "wifi1",
              "Security": "WPA-EAP",
              "EAP": {
                "Outer": "PEAP",
                "Inner": "MSCHAPv2",
                "SaveCredentials": true,
                "Recommended": ["Identity", "Password"]
              }
            }
          }
        ],
        "Type": "UnencryptedConfiguration"
      })",
      kGuidWifi1);
  SetDeviceOpenNetworkConfiguration(kDeviceONC,
                                    /*wait_applied=*/true);

  // Verify that the recommended EAP.Identity of the managed wifi service has
  // not been wiped.
  EXPECT_THAT(shill_service_client_test_->GetServiceProperties(kServiceWifi1),
              Pointee(DictionaryHasValue(shill::kEapIdentityProperty,
                                         base::Value(kUserIdentity))));

  // Verify that the unmanaged wifi service has not been wiped.
  EXPECT_TRUE(shill_profile_client_test_->HasService(kServiceWifi2));
}

IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationEphemeralActionsEnabledTest,
                       EphemeralActions_Active) {
  constexpr char kGuidWifi1[] = "guid_wifi_1";
  AddSharedDevicePolicyMschapv2Service(kServiceWifi1, kGuidWifi1, "wifi1",
                                       kUserIdentity);
  AddPskWifiService(kServiceWifi2, "unmanaged_wifi2_guid", "UnmanagedWifi2",
                    shill::kStateIdle);
  shill_profile_client_test_->AddService(kSharedProfilePath, kServiceWifi2);

  const std::string kDeviceONC = base::StringPrintf(
      R"(
      {
        "GlobalNetworkConfiguration": {
          "RecommendedValuesAreEphemeral": true,
          "UserCreatedNetworkConfigurationsAreEphemeral": true
        },
        "NetworkConfigurations": [
          {
            "GUID": "%s",
            "Type": "WiFi",
            "Name": "Managed wifi1",
            "WiFi": {
              "HexSSID": "7769666931", // "wifi1"
              "SSID": "wifi1",
              "Security": "WPA-EAP",
              "EAP": {
                "Outer": "PEAP",
                "Inner": "MSCHAPv2",
                "SaveCredentials": true,
                "Recommended": ["Identity", "Password"]
              }
            }
          }
        ],
        "Type": "UnencryptedConfiguration"
      })",
      kGuidWifi1);
  SetDeviceOpenNetworkConfiguration(kDeviceONC,
                                    /*wait_applied=*/true);

  // Verify that the recommended EAP.Identity of the managed wifi service has
  // been wiped.
  std::optional<std::string> new_service_path =
      shill_service_client_test_->FindServiceMatchingGUID(kGuidWifi1);
  ASSERT_TRUE(new_service_path);
  {
    const base::DictValue* shill_properties =
        shill_service_client_test_->GetServiceProperties(*new_service_path);
    ASSERT_TRUE(shill_properties);
    EXPECT_THAT(shill_properties->FindString(shill::kEapIdentityProperty),
                IsNull());
    // Also check some other property to see that it has not been wiped.
    EXPECT_THAT(shill_properties->FindString(shill::kEapPhase2AuthProperty),
                Pointee(Eq("auth=MSCHAPV2")));
  }

  // Verify that the unmanaged wifi service has been wiped.
  EXPECT_FALSE(shill_profile_client_test_->HasService(kServiceWifi2));

  // Set EAP.Identity back to a non-empty value.
  shill_service_client_test_->SetServiceProperty(*new_service_path,
                                                 shill::kEapIdentityProperty,
                                                 base::Value(kUserIdentity));

  // Simulate that the device goes to sleep and wakes up, and expect that it
  // leads to this entry getting deleted and then re-created without
  // "EAP.Identity" again. Internally this process is implemented as a policy
  // application, so we can use that to detect the end of the process.
  ScopedNetworkPolicyApplicationObserver network_policy_application_observer;
  chromeos::FakePowerManagerClient::Get()->SendSuspendDone(base::Minutes(10));
  network_policy_application_observer.WaitPoliciesApplied(
      /*userhash=*/std::string());

  // Verify that the recommended EAP.Identity of the recreated managed wifi
  // service has been wiped again.
  new_service_path =
      shill_service_client_test_->FindServiceMatchingGUID(kGuidWifi1);
  ASSERT_TRUE(new_service_path);
  {
    const base::DictValue* shill_properties =
        shill_service_client_test_->GetServiceProperties(*new_service_path);
    ASSERT_TRUE(shill_properties);
    EXPECT_THAT(shill_properties->FindString(shill::kEapIdentityProperty),
                IsNull());
  }

  // Simulate user log-in
  LoginUser(test_account_id_);

  // Set EAP.Identity back to a non-empty value.
  shill_service_client_test_->SetServiceProperty(*new_service_path,
                                                 shill::kEapIdentityProperty,
                                                 base::Value(kUserIdentity));

  // Simulate that the device goes to sleep and wakes up.
  chromeos::FakePowerManagerClient::Get()->SendSuspendDone(base::Minutes(10));
  base::RunLoop().RunUntilIdle();

  // Verify that the recommended EAP.Identity of the managed wifi service has
  // not been wiped because the "ephemeral actions" don't apply within active
  // sessions.
  EXPECT_THAT(
      shill_service_client_test_->GetServiceProperties(*new_service_path),
      Pointee(DictionaryHasValue(shill::kEapIdentityProperty,
                                 base::Value("user_identity"))));
}

IN_PROC_BROWSER_TEST_F(
    NetworkPolicyApplicationEphemeralActionsEnabledUnenrolledTest,
    NetworkPolicyApplicationEphemeralActionsDisabledTest) {
  constexpr char kGuidWifi1[] = "guid_wifi_1";
  AddSharedDevicePolicyMschapv2Service(kServiceWifi1, kGuidWifi1, "wifi1",
                                       kUserIdentity);
  AddPskWifiService(kServiceWifi2, "unmanaged_wifi2_guid", "UnmanagedWifi2",
                    shill::kStateIdle);
  shill_profile_client_test_->AddService(kSharedProfilePath, kServiceWifi2);

  // The device changes state to enterprise-enrolled here, simulating enterprise
  // enrollment with incoming device policy.
  MarkEnterpriseEnrolled();
  const std::string kDeviceONC = base::StringPrintf(
      R"(
      {
        "GlobalNetworkConfiguration": {
          "RecommendedValuesAreEphemeral": true,
          "UserCreatedNetworkConfigurationsAreEphemeral": true
        },
        "NetworkConfigurations": [
          {
            "GUID": "%s",
            "Type": "WiFi",
            "Name": "Managed wifi1",
            "WiFi": {
              "HexSSID": "7769666931", // "wifi1"
              "SSID": "wifi1",
              "Security": "WPA-EAP",
              "EAP": {
                "Outer": "PEAP",
                "Inner": "MSCHAPv2",
                "SaveCredentials": true,
                "Recommended": ["Identity", "Password"]
              }
            }
          }
        ],
        "Type": "UnencryptedConfiguration"
      })",
      kGuidWifi1);
  SetDeviceOpenNetworkConfiguration(kDeviceONC,
                                    /*wait_applied=*/true);

  // Verify that the unmanaged wifi service has not been wiped.
  EXPECT_TRUE(shill_profile_client_test_->HasService(kServiceWifi2));

  // Simulate that the device goes to sleep and wakes up, and check that the
  // unmanaged wifi gets wiped then.
  ScopedNetworkPolicyApplicationObserver network_policy_application_observer;
  chromeos::FakePowerManagerClient::Get()->SendSuspendDone(base::Minutes(10));
  network_policy_application_observer.WaitPoliciesApplied(
      /*userhash=*/std::string());

  EXPECT_FALSE(shill_profile_client_test_->HasService(kServiceWifi2));
}

IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationEphemeralActionsDisabledTest,
                       EphemeralActions_Active) {
  constexpr char kGuidWifi1[] = "guid_wifi_1";
  AddSharedDevicePolicyMschapv2Service(kServiceWifi1, kGuidWifi1, "wifi1",
                                       kUserIdentity);
  AddPskWifiService(kServiceWifi2, "unmanaged_wifi2_guid", "UnmanagedWifi2",
                    shill::kStateIdle);
  shill_profile_client_test_->AddService(kSharedProfilePath, kServiceWifi2);

  const std::string kDeviceONC = base::StringPrintf(
      R"(
      {
        "GlobalNetworkConfiguration": {
          "RecommendedValuesAreEphemeral": true,
          "UserCreatedNetworkConfigurationsAreEphemeral": true
        },
        "NetworkConfigurations": [
          {
            "GUID": "%s",
            "Type": "WiFi",
            "Name": "Managed wifi1",
            "WiFi": {
              "HexSSID": "7769666931", // "wifi1"
              "SSID": "wifi1",
              "Security": "WPA-EAP",
              "EAP": {
                "Outer": "PEAP",
                "Inner": "MSCHAPv2",
                "SaveCredentials": true,
                "Recommended": ["Identity", "Password"]
              }
            }
          }
        ],
      "Type": "UnencryptedConfiguration"
      })",
      kGuidWifi1);
  SetDeviceOpenNetworkConfiguration(kDeviceONC,
                                    /*wait_applied=*/true);

  // Verify that the recommended EAP.Identity of the managed wifi service has
  // not been wiped.
  EXPECT_THAT(shill_service_client_test_->GetServiceProperties(kServiceWifi1),
              Pointee(DictionaryHasValue(shill::kEapIdentityProperty,
                                         base::Value(kUserIdentity))));

  // Verify that the unmanaged wifi service has not been wiped.
  EXPECT_TRUE(shill_profile_client_test_->HasService(kServiceWifi2));
}

IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
                       CheckCaptivePortal_AllValues) {
  constexpr char kGuidWifiTrue[] = "guid_wifi_true";
  constexpr char kGuidWifiFalse[] = "guid_wifi_false";
  constexpr char kGuidWifiHTTPOnly[] = "guid_wifi_http_only";

  constexpr char kNetworkNameTrue[] = "NetworkTrue";
  constexpr char kNetworkNameFalse[] = "NetworkFalse";
  constexpr char kNetworkNameHTTPOnly[] = "NetworkHTTPOnly";

  constexpr char kWifiNameTrue[] = "WifiTrue";
  constexpr char kWifiNameFalse[] = "WifiFalse";
  constexpr char kWifiNameHTTPOnly[] = "WifiHTTPOnly";

  AddPskWifiService(kServiceWifi1, kGuidWifiTrue, kWifiNameTrue,
                    shill::kStateIdle);
  AddPskWifiService(kServiceWifi2, kGuidWifiFalse, kWifiNameFalse,
                    shill::kStateIdle);
  AddPskWifiService(kServiceWifi3, kGuidWifiHTTPOnly, kWifiNameHTTPOnly,
                    shill::kStateIdle);

  static constexpr char kConfig[] = R"(
      {
        "GlobalNetworkConfiguration": {
        },
        "NetworkConfigurations": [
          {
            "GUID": "%s",
            "Name": "%s",
            "Type": "WiFi",
            "CheckCaptivePortal": "%s",
            "WiFi": {
              "AutoConnect": true,
              "HiddenSSID": false,
              "Passphrase": "DeviceLevelWifiPwd",
              "SSID": "%s",
              "Security": "WPA-PSK"
            }
          },
          {
            "GUID": "%s",
            "Name": "%s",
            "Type": "WiFi",
            "CheckCaptivePortal": "%s",
            "WiFi": {
              "AutoConnect": true,
              "HiddenSSID": false,
              "Passphrase": "DeviceLevelWifiPwd",
              "SSID": "%s",
              "Security": "WPA-PSK"
            }
          },
          {
            "GUID": "%s",
            "Name": "%s",
            "Type": "WiFi",
            "CheckCaptivePortal": "%s",
            "WiFi": {
              "AutoConnect": true,
              "HiddenSSID": false,
              "Passphrase": "DeviceLevelWifiPwd",
              "SSID": "%s",
              "Security": "WPA-PSK"
            }
          }
        ],
        "Type": "UnencryptedConfiguration"
      })";

  const std::string kDeviceONC = base::StringPrintf(
      kConfig, kGuidWifiTrue, kNetworkNameTrue,
      ::onc::check_captive_portal::kTrue, kWifiNameTrue, kGuidWifiFalse,
      kNetworkNameFalse, ::onc::check_captive_portal::kFalse, kWifiNameFalse,
      kGuidWifiHTTPOnly, kNetworkNameHTTPOnly,
      ::onc::check_captive_portal::kHTTPOnly, kWifiNameHTTPOnly);
  SetDeviceOpenNetworkConfiguration(kDeviceONC,
                                    /*wait_applied=*/true);

  // Verify that the CheckCaptivePortal of the managed Wi-Fi services are set
  // correctly.
  EXPECT_THAT(shill_service_client_test_->GetServiceProperties(kServiceWifi1),
              Pointee(DictionaryHasValue(shill::kCheckPortalProperty,
                                         base::Value("true"))));

  EXPECT_THAT(shill_service_client_test_->GetServiceProperties(kServiceWifi2),
              Pointee(DictionaryHasValue(shill::kCheckPortalProperty,
                                         base::Value("false"))));

  EXPECT_THAT(shill_service_client_test_->GetServiceProperties(kServiceWifi3),
              Pointee(DictionaryHasValue(shill::kCheckPortalProperty,
                                         base::Value("http-only"))));
}

// Tests that when
// - a policy-provided network has a ClientCertPattern which contains an
//   EnrollmentURI and
// - the ClientCertPattern doesn't match any installed client certificate,
// then, on a connection attempt through the system tray, a dialog is triggered
// which suggests to enroll a client certificate (the somewhat confusingly named
// "enrollment dialog" - it's not related to enterprise enrollment).
// Also tests that when accepting that dialog, a browser tab is opened,
// navigating to the provided EnrollmentURI.
//
// This is a regression test for b/319188170.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
                       ClientCertEnrollmentUriTriggered) {
  Add8021xWifiService(kServiceWifi1, "UserLevelWifiGuidOrig",
                      "UserLevelWifiSsid", shill::kStateIdle);

  LoginUser(test_account_id_);
  const std::string user_hash = user_manager::UserManager::Get()
                                    ->FindUser(test_account_id_)
                                    ->username_hash();
  shill_profile_client_test_->AddProfile(kUserProfilePath, user_hash);

  // Set a policy that uses a ClientCertPattern which has an EnrollmentURI and
  // will not resolve to any client certificate (no client certificate has been
  // installed/imported at all).
  static constexpr char kUserONC[] = R"(
    {
      "NetworkConfigurations": [
        {
          "GUID": "{user-policy-for-wifi}",
          "Name": "OncPolicyToSelectClientCert",
          "Type": "WiFi",
          "WiFi": {
             "EAP":  {
              "Outer": "EAP-TLS",
              "ClientCertType": "Pattern",
              "Identity": "SomeIdentity",
              "ClientCertPattern": {
                "Issuer": {
                  "CommonName": "DoesntMatchAnything"
                },
                "EnrollmentURI": ["chrome://version"]
              }
             },
             "SSID": "UserLevelWifiSsid",
             "Security": "WPA-EAP"
          }
        }
      ]
    })";
  SetUserOpenNetworkConfiguration(user_hash, kUserONC,
                                  /*wait_applied=*/true);

  // Click on the SSID in the system tray and expect the (client certificate)
  // enrollment dialog to appear.
  views::NamedWidgetShownWaiter dialog_widget_waiter(
      views::test::AnyWidgetTestPasskey(), ash::enrollment::kWidgetName);
  ASSERT_NO_FATAL_FAILURE(ConnectToSsidUsingSystemTray("UserLevelWifiSsid"));

  views::Widget* dialog_widget = dialog_widget_waiter.WaitIfNeededAndGet();
  ASSERT_TRUE(dialog_widget);

  // Accept the enrollment dialog and expect a corresponding tab with the
  // EnrollmentURI to be opened.
  ui_test_utils::AllBrowserTabAddedWaiter tab_waiter;
  ASSERT_NO_FATAL_FAILURE(AcceptCertEnrollmentDialog(dialog_widget));

  content::WebContents* const tab_contents = tab_waiter.Wait();
  ASSERT_TRUE(tab_contents);

  EXPECT_EQ(tab_contents->GetVisibleURL(), GURL("chrome://version"));
}

}  // namespace policy
